<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>zhen&#x27;s everything</title>
    <link rel="self" type="application/atom+xml" href="https://zhen.wang/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://zhen.wang"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-02-28T00:00:00+00:00</updated>
    <id>https://zhen.wang/atom.xml</id>
    <entry xml:lang="en">
        <title>AI狂欢下的隐忧：数字泔水泛滥与互联网生态的三重困境</title>
        <published>2026-02-28T00:00:00+00:00</published>
        <updated>2026-02-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/test/"/>
        <id>https://zhen.wang/article/test/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/test/">&lt;p&gt;生成式AI如潮水般席卷互联网全域，在为内容创作铺就便捷坦途、赋能信息传播提质增效的同时，也悄然催生了一场愈演愈烈的“垃圾围城”之困。2025年末，韦氏词典将“slop”（泔水）遴选为年度热词，精准锚定“AI批量生成的低质量数字内容”这一核心内涵，恰如其分地戳破了AI技术狂欢背后的深层隐忧——曾经汇聚优质信息的互联网宝库，正逐步沦为劣质丛生、陈腐堆积的AI垃圾场，而内容洪流的奔涌而至，引发存储需求的井喷式增长，更让这场生态危机雪上加霜、愈陷愈深。&lt;&#x2F;p&gt;
&lt;p&gt;AI技术的普惠之风，大幅消融了内容生产的门槛壁垒，致使低质量内容如荒草般规模化蔓延，持续侵蚀着网络生态的纯净底色。传统创作之路，素来需深耕细作、厚积薄发，历经反复打磨方得精品；而生成式AI仅凭几句简单指令，便可在弹指之间生成文章、视频、图像等多元内容，门槛的骤降，催生了海量无价值的无效创作。电商平台上，低成本AI内容制作教程的热销，助推无数创作者陷入批量复制的怪圈，所产出的内容，或为渲染不良价值观的爽文，或为篡改经典的粗制改编，或为编造虚假信息的短视频，皆难逃逻辑紊乱、导向偏颇的桎梏，却能借猎奇之风收割流量、牟取私利。更令人忧心的是，这些劣质内容常被纳入AI训练的数据库，形成“垃圾投喂垃圾”的恶性循环，让优质信息被漫天糟粕所裹挟，用户在信息海洋中探寻真理的成本，也随之陡增。&lt;&#x2F;p&gt;
&lt;p&gt;“重生成、轻维护”的失衡模式，让大量AI生成内容在时光流转中快速褪色、沦为尘泥，进一步加重了网络生态的负重之困。AI工具的便捷性，让部分创作者陷入“数量至上”的迷思，一味追逐内容产出的速度与规模，却将后续的更新维护抛诸脑后。无论是个人创作者借AI落笔的行业科普，还是企业依托AI搭建的知识矩阵，多难逃“一经生成，便遭搁置”的宿命。随着时代迭代、信息更新，这些内容渐渐褪去价值，沦为无人问津、无人打理的数字残骸。此类乱象，在社交平台、知识社区中俯拾皆是，大量未被悉心维护的AI内容，因数据滞后、观点陈腐，非但无法传递有效价值，更可能误导公众认知，成为网络生态治理路上难以逾越的荆棘。&lt;&#x2F;p&gt;
&lt;p&gt;内容总量的爆炸式激增，如奔涌的江河般冲击着互联网的存储体系，催生了新一轮的资源消耗危机。AI生成内容的低成本、高产出特质，推动互联网数据量呈指数级攀升，每一条内容的存储，皆需耗费大量的硬件资源与能源成本，不可谓不珍贵。根据国际数据公司（IDC）预测（数据来源：财联社2025年6月4日、中国经营报2025年7月12日转载IDC报告），2025年全球数据生成总量将抵达213.56ZB的峰值，至2029年更将翻倍扩容，攀升至527.47ZB；其中中国市场，2025年数据量约为51.78ZB，2029年将稳步增长至136.12ZB，复合增速高达26.9%。在这海量数据之中，相当一部分是AI生成的垃圾内容，却占据着寸土寸金的存储资源——普通服务器仅需数百GB内存便足以支撑运转，而AI服务器的内存需求却高达数TB级别，是传统服务器的数倍乃至十几倍，HBM（高带宽内存）等高端存储产品的需求爆发，正是AI存储压力最直观的彰显。加之存储厂商的产能增长步履缓慢，2026年DRAM与NAND供给仍处于紧平衡态势，垃圾内容的泛滥，无疑是对稀缺存储资源的无端耗费，更持续推高着互联网运营的成本负担。&lt;&#x2F;p&gt;
&lt;p&gt;AI本应是赋能内容创作、优化信息传播的时代利器，是点亮创意、传递价值的桥梁，却因滥用无度、监管缺位，沦为互联网垃圾的主要滋生土壤与传播载体。这场生态危机的背后，是技术滥用催生的创作浮躁，是流量至上裹挟的利益执念，更是“重生成、轻维护”的认知偏差与行为失衡。若算法始终执着于点击量与完播率的追逐，一味推送低质内容、劣币驱逐良币；若创作者深陷短期利益的迷潭，批量炮制无维护价值的AI内容，漠视创作本心；若存储资源持续被垃圾内容肆意侵占，优质内容的生存空间被不断挤压，那么互联网的核心价值，终将在这场垃圾洪流中逐渐消解、黯然失色。&lt;&#x2F;p&gt;
&lt;p&gt;破解AI垃圾引发的生态困局，并非要否定AI技术本身的价值，核心在于构建科学完善的内容管理体系，划定规范有序的使用边界。创作者当摒弃数量导向的浮躁，坚守质量为本的初心，合理运用AI工具，用心打磨内容、做好后续的更新与维护，让每一份创作都承载价值；平台当优化算法机制，抬高低质内容的审核门槛，推行AI内容水印与溯源机制，为优质内容搭建传播桥梁，让精品得以彰显；行业层面当建立统一的内容管理规范，厘清创作边界、遏制乱象滋生，打破“垃圾投喂垃圾”的恶性循环。唯有多方协同、同向发力，方能让AI回归赋能本质，让互联网挣脱垃圾围城的桎梏，重焕优质信息聚合与传播的生机与活力，回归其本该拥有的澄澈与价值。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>时代，写作的本质回归：从完整创作到观点锚定</title>
        <published>2026-01-15T00:00:00+00:00</published>
        <updated>2026-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/AI 时代，写作的本质回归：从完整创作到观点锚定/"/>
        <id>https://zhen.wang/article/AI 时代，写作的本质回归：从完整创作到观点锚定/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/AI 时代，写作的本质回归：从完整创作到观点锚定/">&lt;p&gt;当生成式 AI 如潮水般涌入写作场景，从一篇完整的报告到一段精炼的文案，只需输入简单指令便能快速生成，很多人开始困惑：AI 时代，人类的写作价值究竟在哪里？事实上，AI 的普及并未消解写作的意义，反而推动写作模式发生了一场深刻的范式转移 —— 从 “从零到一的完整创作”，转变为 “观点提炼为先，AI 辅助扩展” 的全新形态，让写作回归到 “观点表达” 这一最本质的核心。&lt;&#x2F;p&gt;
&lt;p&gt;在 AI 未普及的时代，写作是一场 “孤独的全程战役”。无论是学生撰写论文、职场人起草方案，还是创作者输出文章，都需要从空白页面开始，完成观点提炼、逻辑梳理、语言组织、内容扩充的全流程。这个过程中，很多人会陷入两个困境：要么被 “写完整篇” 的压力困住，迟迟无法动笔；要么在语言表达、内容填充上耗费大量精力，反而弱化了核心观点的传递。更值得注意的是，为了提升写作能力，我们还需要主动学习各类文章的框架，逐字逐句记录摘抄文章的精髓片段，反复揣摩借鉴，耗时费力却未必能快速掌握精髓。此时的写作，更像是一场 “体力与脑力的双重消耗”，我们花了太多时间在 “如何写”“如何学” 上，却常常忽略了 “要写什么”。
而 AI 的出现，彻底打破了这种困境，让写作的重心发生了根本性转移。如今，越来越多的人发现，高效的写作不再是 “自己写完整篇文章”，而是先拿出自己的核心思考 —— 一个观点、一个灵感、一个判断，也就是我们所说的 “观点临念”，再借助 AI 工具，将这个临念进行扩展、深化、润色，最终形成一篇完整的文章。更重要的是，以往需要我们花费大量时间去学习的文章框架、摘抄积累的精髓片段，在 AI 时代都能由 AI 为我们完成输出：只需输入相关需求，AI 就能快速拆解各类文章的逻辑框架，提炼核心片段，甚至根据我们的观点匹配适配的框架与素材，省去了我们逐字摘抄、反复揣摩的繁琐过程。这种模式下，人类摆脱了繁琐的内容填充、语言打磨以及素材积累，得以将全部精力集中在 “提炼观点” 上，而 AI 则成为了高效的 “执行助手”，完成观点的延伸、素材的整合与落地。&lt;&#x2F;p&gt;
&lt;p&gt;这种转变，看似是写作流程的简化，实则是对写作本质的回归。写作的核心从来不是 “文字的堆砌”，而是 “观点的传递”。无论是一篇议论文、一份工作报告，还是一篇随笔，真正有价值的，永远是作者想要表达的核心观点 —— 是对某个问题的独特见解，是对某种现象的深刻反思，是对某个方向的明确判断。AI 可以生成流畅的文字、严谨的逻辑、丰富的案例，也能高效输出文章框架、提炼精髓片段，但它无法替代人类的思考，无法产生属于 “个人” 的、带有温度和独特视角的观点。就像同样一个观点，不同的人提炼的角度不同，传递的态度不同，AI 扩展出的内容也会呈现出截然不同的气质；而如果没有明确的观点，即便 AI 生成了看似完整的文章，也只是空洞的文字组合，没有任何思想价值。&lt;&#x2F;p&gt;
&lt;p&gt;在 AI 时代，“观点提炼能力” 已经成为写作的核心竞争力，甚至是个人核心能力的重要组成部分。以前，我们可能会羡慕那些 “下笔如有神”、能快速写出完整文章的人，也会佩服那些积累了大量写作素材、熟练掌握各类框架的人；而现在，真正稀缺的，是那些能在纷繁复杂的信息中，快速捕捉核心、提炼独特观点的人。因为文字可以由 AI 生成，文章框架和精髓片段可以由 AI 提炼，但观点只能由人类创造 —— 它源于我们的生活经历、知识储备、思维方式，源于我们对世界的观察与思考，这是 AI 无法复制的核心价值。
有人担心，这种 “观点 + AI” 的模式，会让人类的写作能力退化，变得越来越依赖 AI，最终失去独立写作的能力。其实这种担心大可不必。真正的写作能力，从来不是 “记住多少华丽的辞藻”“掌握多少写作技巧”，也不是 “背诵多少文章框架、摘抄多少精髓片段”，而是 “拥有独立思考的能力” 和 “清晰表达观点的能力”。AI 只是一个工具，它能帮我们完成繁琐的执行工作、素材积累工作，却无法替代我们思考；相反，它能让我们从繁琐的事务中解放出来，有更多时间去沉淀、去思考、去提炼更有价值的观点，进而提升我们的核心思考能力。就像计算器的出现，没有让人类的计算能力退化，反而让我们能专注于更复杂的数学研究一样，AI 的出现，也让写作回归本质，让观点成为写作的核心。&lt;&#x2F;p&gt;
&lt;p&gt;我们可以看到，如今越来越多的职场人、创作者、学习者，已经开始适应这种全新的写作模式。职场人会先提炼出方案的核心观点和执行思路，再用 AI 扩展成完整的方案，同时借助 AI 快速匹配适配的方案框架；创作者会先捕捉到一个灵感、一个核心观点，再借助 AI 丰富内容、优化表达，无需再手动摘抄同类作品的精髓片段；学习者会先梳理出知识点的核心逻辑，再用 AI 扩展成详细的笔记，AI 也能同步输出相关知识点的梳理框架与核心片段。这种模式，不仅提高了写作效率，更让每一篇文章都有了明确的核心和灵魂，避免了空洞无物的内容堆砌。&lt;&#x2F;p&gt;
&lt;p&gt;AI 时代，写作不再是 “一个人的战斗”，而是 “人类观点与 AI 工具的协同共生”。我们不必再为 “写不完”“写不好” 而焦虑，也不必担心 AI 会替代人类的写作 —— 因为 AI 能替代的，是 “写” 的过程，是素材积累、框架梳理的过程，却替代不了 “想” 的过程；能替代的，是空洞的文字，却替代不了有温度、有思想的观点。
未来，写作的价值将越来越集中在 “观点提炼” 上：你的观点是否独特，是否有深度，是否能引发共鸣，将成为衡量一篇文章价值的核心标准。而 AI，将成为我们最得力的助手，帮助我们把每一个有价值的观点，都扩展成一篇有温度、有力量、有思想的文章，同时为我们省去框架学习、素材摘抄的繁琐，让我们更专注于核心思考。&lt;&#x2F;p&gt;
&lt;p&gt;在这个大 AI 时代，我们不必抗拒技术的变革，更不必担忧自身价值的丧失。相反，我们应该学会拥抱 AI，将它作为提升写作效率、传递核心观点的工具，专注于锤炼自己的观点提炼能力，让写作回归本质，让每一个观点都能通过文字的力量，被看见、被传递、被共鸣。毕竟，文字会过时，技巧会迭代，甚至 AI 生成的框架和素材也会不断更新，但那些源于人类思考的独特观点，永远是写作最珍贵的灵魂。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>离开JetBrains</title>
        <published>2025-12-31T00:00:00+00:00</published>
        <updated>2025-12-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/离开JetBrains/"/>
        <id>https://zhen.wang/article/离开JetBrains/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/离开JetBrains/">&lt;p&gt;几年前，我认真想过一次“叛逃”。&lt;&#x2F;p&gt;
&lt;p&gt;从 JetBrains 全家桶，转向更轻量的工具，比如 VS Code，甚至 Zed。那时候的我，理性上已经开始动摇，情感上却还在依赖。工作节奏快、项目复杂、deadline 像钟摆一样无情摆动——切换工具意味着学习成本、环境重建、快捷键肌肉记忆崩塌。于是每次都只是装一下 VS Code，配几个插件，惊叹两天，然后默默切回 IntelliJ。&lt;&#x2F;p&gt;
&lt;p&gt;浅尝则止，是成年人对“改变”的常见态度。&lt;&#x2F;p&gt;
&lt;p&gt;但最近一两年，我明显感受到 JetBrains 系列在走一条让我不太舒服的路。臃肿、启动慢、内存占用高，偶发卡顿，某些存在已久的 BUG 长期得不到修复。你能在 Reddit 上看到不少类似的抱怨：性能退化、升级后变慢、索引失控、插件冲突……当然，每个大型软件都会有这些问题，但当“重”成为一种常态，开发体验就开始被侵蚀。&lt;&#x2F;p&gt;
&lt;p&gt;更关键的是——时代变了。&lt;&#x2F;p&gt;
&lt;p&gt;AI 不是一个“插件功能”，而是一种开发范式的改变。&lt;&#x2F;p&gt;
&lt;p&gt;JetBrains 当然也在接入 AI，但它的架构天生更像传统 IDE 的延伸：强类型、深度索引、工程级抽象、静态分析驱动一切。这套体系在没有 AI 的时代是王者——自动补全精准、重构安全、代码导航强大。但在 AI 时代，很多“深度理解”已经可以由大模型完成，而且是跨语言、跨框架、跨上下文的。&lt;&#x2F;p&gt;
&lt;p&gt;当 AI 可以理解整段逻辑甚至整个仓库时，传统 IDE 的优势正在被稀释。&lt;&#x2F;p&gt;
&lt;p&gt;与此同时，JetBrains 的商业模式也让我开始犹豫。订阅制、价格不低。我已经连续续费了 5 年，说实话是一种惯性。熟悉的快捷键、熟悉的界面、熟悉的工作流——人类的大脑天然抗拒迁移。但今年，我决定不再续费。&lt;&#x2F;p&gt;
&lt;p&gt;不是因为它完全不好，而是因为它不再是“最优解”。&lt;&#x2F;p&gt;
&lt;p&gt;不过必须承认，JetBrains 也有一些让我至今念念不忘的设计。其中一个，就是那个几乎成了“肌肉记忆信仰”的功能——double shift。&lt;&#x2F;p&gt;
&lt;p&gt;连续按两下 Shift，Search Everywhere。&lt;&#x2F;p&gt;
&lt;p&gt;这个设计优雅到近乎哲学。它不是简单的文件搜索，而是一种“全局认知入口”。类、文件、Symbol、Action、配置项、Git 操作……你不需要记得它在哪个菜单，不需要记得快捷键，只要 double shift，然后输入几个模糊字符，世界就会自己收敛到你面前。&lt;&#x2F;p&gt;
&lt;p&gt;那一刻，你和工程之间没有层级结构，只有意图。&lt;&#x2F;p&gt;
&lt;p&gt;这种体验，其实是一种高度抽象后的统一接口。它把复杂 IDE 的所有能力压缩成一个搜索框。这种“极致统一入口”的设计思路，是 JetBrains 工程文化里非常闪光的一点。&lt;&#x2F;p&gt;
&lt;p&gt;VS Code 当然也有 Command Palette，也有全局搜索，但在整体一致性和细腻程度上，仍然差那么一点“浑然一体”的感觉。JetBrains 的 double shift 像是一种认知捷径，而不仅仅是功能入口。&lt;&#x2F;p&gt;
&lt;p&gt;这也是迁移过程中最让我不舍的部分。&lt;&#x2F;p&gt;
&lt;p&gt;但工具的演化，从来不是单点功能的比较，而是整体范式的迁移。&lt;&#x2F;p&gt;
&lt;p&gt;我开始逐步迁移到 VS Code。&lt;&#x2F;p&gt;
&lt;p&gt;VS Code 是一个很有意思的存在。它既不是传统意义上的文本编辑器，也不完全是重型 IDE。它更像一个“可编程外壳”——一个可以被插件不断重塑的开发平台。默认状态下它是轻量的，但通过插件你可以让它变成几乎任何形态。&lt;&#x2F;p&gt;
&lt;p&gt;最关键的是，它的 AI 插件生态极其活跃。&lt;&#x2F;p&gt;
&lt;p&gt;Copilot、各类对话式编程助手、上下文感知补全、基于大模型的重构建议……AI 插件的迭代速度远超传统 IDE 的更新节奏。某种意义上，VS Code 成为了 AI 开发工具创新的试验场。&lt;&#x2F;p&gt;
&lt;p&gt;当然，迁移不是没有代价。&lt;&#x2F;p&gt;
&lt;p&gt;JetBrains 的 Git Client 是我最怀念的部分之一。分支管理、rebase 可视化、冲突解决体验，都非常成熟。而 VS Code 原生 Git 功能，只能说“够用”，但不优雅。&lt;&#x2F;p&gt;
&lt;p&gt;于是我做了一个有点“分体式”的选择：VS Code + GitKraken Pro。&lt;&#x2F;p&gt;
&lt;p&gt;GitKraken 在 Git 交互体验上确实优秀，图形化操作直观、rebase 流程清晰、冲突处理友好。开通 Pro 版本后，一些团队协作能力也更完善。某种程度上，我把 JetBrains 的“全能 IDE”模式拆解成了“可组合工具链”。&lt;&#x2F;p&gt;
&lt;p&gt;这其实反映了一个更大的趋势：单体式 IDE 正在被模块化工具链取代。&lt;&#x2F;p&gt;
&lt;p&gt;过去我们追求“一体化”，因为整合意味着效率。现在我们追求“可组合”，因为 AI 让工具之间的边界变得柔软。编辑器负责交互，AI 负责理解，专用工具负责专业能力。每个组件只做好一件事。&lt;&#x2F;p&gt;
&lt;p&gt;更重要的是，VS Code 的性能模型更轻。内存占用更可控，启动更快，插件加载可管理。它不像一个庞大的工程机器，而更像一个不断进化的生态系统。&lt;&#x2F;p&gt;
&lt;p&gt;这不是情绪化的“JetBrains 已死”宣言。JetBrains 依然强大，尤其在大型 Java&#x2F;Kotlin 项目、复杂重构场景下仍有不可替代的优势。它的 double shift、强类型重构、结构化导航，仍然是工业级打磨的代表。但在 AI 驱动开发的今天，灵活性、插件生态、开放性正在成为新的核心竞争力。&lt;&#x2F;p&gt;
&lt;p&gt;工具选择本质上是一种“认知结构”的选择。&lt;&#x2F;p&gt;
&lt;p&gt;你是希望工具替你构建完整的工程抽象？还是希望工具成为 AI 的接口，让你更自由地组合能力？我选择了后者。&lt;&#x2F;p&gt;
&lt;p&gt;也许几年后我会再次迁移。开发工具的历史，本质上就是抽象层不断上移的历史。从 Vim 到 IDE，从 IDE 到 AI 协作环境。我们并不是在选择编辑器，而是在选择与代码互动的方式。&lt;&#x2F;p&gt;
&lt;p&gt;当 AI 能够理解上下文、生成重构建议、解释陌生代码、甚至协助架构设计时，工具的核心不再是“功能多强”，而是“与智能协作的摩擦有多小”。&lt;&#x2F;p&gt;
&lt;p&gt;对我来说，26年将会是一个分水岭。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>随笔</title>
        <published>2025-11-30T00:00:00+00:00</published>
        <updated>2025-11-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/随笔/"/>
        <id>https://zhen.wang/article/随笔/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/随笔/">&lt;p&gt;11月初去了趟日本，在东京待了6天（其实有一天跑去富士山了），每天除了逛还是逛，不过到底是祛魅了，毕竟生活成本太高。另一方面，真的好羡慕霓虹人的童年，你可以在二手市场找到一箩筐一箩筐的GBA，NDS、3DS，甚至是箱说全的PlayStation1，这些足以见得在日本发达的游戏业。话又说回来，目前在计划做独立游戏（其实已经计划很久了），因为要做的游戏的玩法内容需要用到强化学习的知识来支撑，所以现在还在恶补这块的内容。目前计划是明年一季度出试玩demo，无论如何，坚持下去，也算是完成自己的一个游戏梦。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>平台</title>
        <published>2025-10-30T00:00:00+00:00</published>
        <updated>2025-10-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/平台/"/>
        <id>https://zhen.wang/article/平台/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/平台/">&lt;p&gt;有三个人，坐电梯，从一楼到十楼。一个人在原地跑步，一个人在做俯卧撑，一个在用头撞墙。他们都到了十楼，有人问你们是如何到十楼的？一个人说，我是跑上来的。一个说，我是做俯卧撑上来的。一个说，我是用头撞墙上来的。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>前五年</title>
        <published>2025-09-30T00:00:00+00:00</published>
        <updated>2025-09-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/前五年/"/>
        <id>https://zhen.wang/article/前五年/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/前五年/">&lt;p&gt;计算机相关的专业一开始必学C&#x2F;C++，边学边会伴随着一些科班经典课程：数据结构与算法、数据库、计算机网络、汇编等等。等到这些课程学完了以后，就好像没事干了一样，笔者也闲不下来，又不是特别喜欢C&#x2F;C++，于是从网上找了一本《Java编程思想》，大三没课的时候，天天抱着这本书在图书馆啃，早上啃代码，中午吃饭，下午把书里面提到的习题实践一遍，基本上一天就这么度过。等到大三结束的时候，笔者算是掌握了怎么写Java了。也庆幸这一学期的“刻苦”，让我这个上大学前还是个计算机小白的人，逐渐对编程有了基本的认识，也逐渐了解到了毕业以后到底要干什么了。会了Java以后，我又开始去学习如何系统性的开发一些软件，比如使用SSH架构（OMG，当时的经典架构，Spring Struct2 Hibernate）开发Web应用程序，用JSP写Web页面；用Java Swing框架开发客户端软件。在没有如今AI的帮助下，当时各种问题就是baidu，去各种论坛找解决方案。幸亏当时还比较喜欢折腾，真的是非常原始的方式去开发应用，让我如今对于一些开发技术能够很直观地去理解，而不是像现在一些开发者一上来就接触的是比较“黑盒”的框架。&lt;&#x2F;p&gt;
&lt;p&gt;等到毕业以后，我到了一家传统公司工作，招聘时考察Java，进去就去做C#的东西了。这家公司的一些业务技术栈是.net framework框架（还不是现如今的 .net core），专注于Windows桌面客户端程序开发。不过笔者上手C#很快，因为它们太像了，甚至笔者主观上认为C#比Java要好的多。至于为什么C#没太流行，我倾向于微软一开始的让它绑定到Windows 平台以及闭源有很大的关系。使用C#的一年多的时间还挺快乐的，比起服务端程序，这些用C#开发出来的桌面客户端对于笔者来说成就感更大，因为你能看着它一步一步建立起来，是一个很具象的东西。也是在这一年多的时间，对GUI程序的基本模式也更加了解了。比如UI组件的事件机制，GUI程序的事件循环等。后来又接触了WPF，让我好像发现了新大陆，OMG，原来GUI开发的数据驱动是这么舒适。WPF里面的一些思维模式也直接对笔者后来的Web前端开发产生了深刻的影响，以至于后来的上手Vue、React的时候，简直不要太简单。&lt;&#x2F;p&gt;
&lt;p&gt;是的，后来一次偶然的契机，公司让笔者开发一套前后端分离的Web应用的时候，笔者从这个时候开始接触了Web前端应用开发（当然这个时候笔者还要写Java后端服务）。等到这个应用开发完成后，笔者已经对Vue这块基本上掌握了，于是笔者趁热打铁，接下了公司的IM产品重构项目，调研了一番发现了Electron，又使用Electron + Vue开发了一个桌面的IM应用给公司里面的业务方使用。开发完了后，又搞了一阵子的Qt，不过这个时候有一个比较拿的出手的东西就是用Qt结合了CEF做了个客户端渲染H5的框架，在空闲时间把这块的知识整理了下，开源到了Github（放心，没有泄露任何公司业务），也写了一系列的《使用CEF》文章进行了总结，收获了很多也在搞这块的同行的好评，甚至还收到几笔付费咨询。但是这块做的差不多还没收尾的时候，组织架构调整了，我被调到了另一个组开始搞基于React的低代码平台（那年好火）。搞低代码的这段时间很累，概念是新的，还伴随着大老板的压力，整体做下来了，收获了客户，也收获了质疑，质疑这东西的价值。不过我倒是挺沉迷技术的，也就没有在意这些。&lt;&#x2F;p&gt;
&lt;p&gt;在这家公司我学到了很多东西，但大多数都是技术的，都是自学的。因为这家公司是一家大公司的子公司，相当于“内包”，很多需求都是leader通过业务方提出的零散的诉求“想象”出来的，没有专业的PM来进行产品分析。直到去了后来真正意义上的互联网公司，我才真正了解到了一款产品究竟需要多少团队的公共合作才能做好，不过那都是后话。&lt;&#x2F;p&gt;
&lt;p&gt;本来我在第一家公司待的挺好的，公司也给到我很多激励。不过作为白羊座的人，一旦产生了某些想法一些，就会下定决定去完成它，比如离职。为什么产生了离职的想法？因为团队内的一些人事物让我很不舒服，就像我上面说的一样，没有真正的PM和好的leader去让一个产品走向正确的道路，这个团队就会做出一些无效的付出，这些无效的付出会逐渐消磨一个人的精力，人的精力是有限的，并且笔者也不再是少年了。&lt;&#x2F;p&gt;
&lt;p&gt;暂时就写到这吧。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>早上八点半的快餐厅</title>
        <published>2025-08-31T00:00:00+00:00</published>
        <updated>2025-08-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/早上八点半的快餐厅/"/>
        <id>https://zhen.wang/article/早上八点半的快餐厅/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/早上八点半的快餐厅/">&lt;p&gt;大约在11年前我从四川某市一所普通学校高中毕业，成绩只能说一般。当时，第一志愿电子科大沙河校区572分提档，而我差1分遗憾落败，遂被第二志愿的南京邮电大学软件工程专业录取。当然，无论是第一志愿还是第二志愿其实都跟计算机相关，因为父母一直关心未来的发展，托我一个在北京的亲戚分析打听得来的结论。&lt;&#x2F;p&gt;
&lt;p&gt;那时的我根本不懂软件编程到底是什么，只知道要好好学习，毕竟学什么不是学呢。还记得大一的C语言，同宿舍的另一个同学早就了解了什么是编程，在课堂上和老师对答如流。而我根本没有关于编程的概念，只知道，哦这个就是C语言，它需要用某种软件（VC++6.0），在里面编写C语言的代码，然后点击“run”，就能在一个地方看到“hello, world”。至于说什么编译阶段、链接阶段，管他的，在各种试卷上这么写就行（直到很久以后，我才真正的理解了编译，链接，不过那个时候已经是我工作的第三年了）。&lt;&#x2F;p&gt;
&lt;p&gt;尽管没有特别了解编程是什么，但我至少还是认真履行了学生这个角色的基本职责的：每堂课我都很认真听，认真的搭建环境，认真的编写代码。在大一上的期末，指针我还是会用的（笑）。同时，大一下开学，就迎来了第一个挑战。&lt;&#x2F;p&gt;
&lt;p&gt;大一下开学的时候，不知道学校出于什么样的考虑。要在开学的头两周进行所谓的“实验课”，不过对于我们这样的计算机相关专业的同学来说，不会在干净整洁的实验室里面，拿着设备仪器检测这检测那。计算机系的本科实验室，不过是一间摆满了一排又一排桌子的房间而已，我们就带着自己的笔记本，在里面写代码。当时实验课的要求是用C语言写了一个控制台打印的车票管理系统，代码我都保存着的：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-08-31&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;那时候根本没有什么版本控制的概念，也不懂什么叫模块拆分，真就像网上有些段子说的一样，一个&lt;code&gt;.c&lt;&#x2F;code&gt;文件搞定。但至少代码没有什么问题，我现在码字的mac上也能编译出来：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-08-31&#x2F;020.gif&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;是的，效果就是这么朴实无华。不过这一次的实验课，倒是让我对编程的概念有了更加进一步的认识，同时对于编程的基本功也有了提升。&lt;&#x2F;p&gt;
&lt;p&gt;大二的时候，开始接触到了数据结构这门课程。一开始我很茫然，怎么也无法get到什么是数据结构。那个时候我一直觉得数据结构就是一个实实在在的，存在于C语言里面的，某个模块。要用数据结构，就要在C语言里面编程使用。直到大概学到了一半的时候，我才慢热地知道了它是一种思想概念。这个时候接触到的栈、队列、树、图等数据结构，在今天的我看来意义重大（废话），不过那个时候我还是学生思想，知道是什么，大概知道怎么写就行，只有作业需要的时候才会用，平常编写代码就是数组走天下，程序一开始就是用宏定义常量，然后申请数组空间，巴拉巴拉开始写代码。&lt;&#x2F;p&gt;
&lt;p&gt;突然想起，这一阶段还有一个很重要的内容就是开始学习C++了。这个时候我们开始接触OOP（面向对象编程），不过一开始也是让我摸不着头脑。什么”类包含数据和行为“，什么“一只狗有四条腿”，什么“狗继承自动物这个类“，什么”类的实例化“等等，现在想起来也是有趣。&lt;&#x2F;p&gt;
&lt;p&gt;再到大三软件工程这门课的时候，期末作业是要求我们按照软件工程瀑布模式完整的做一个项目。我和另一位同学一拍即合，决定搞一个游戏出来。但那时的我们还完全不懂什么游戏引擎，想来想去决定使用Qt5来做一款类似于劲舞团的&lt;strong&gt;赛马&lt;&#x2F;strong&gt;游戏（具体怎么玩后面会讲）。虽然现在想起来真的太难绷了，但那时我们一整个学期做这个项目的时候，干的非常开心。&lt;&#x2F;p&gt;
&lt;p&gt;我当时的工作主要有两个：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;逻辑代码&lt;&#x2F;li&gt;
&lt;li&gt;画UI&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;这个项目做完，我基本掌握了PhotoShop的使用（哈哈哈哈）。幸运的是，这款“游戏”的源代码，原始的素材物料都得以保存：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-08-31&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;没有AI的年代，“古法手工”的马儿外形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-08-31&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;2016年（9年前）的赛马游戏源代码：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-08-31&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;讲了这么多的，这个东西到底怎么玩呢？别着急，作为“专业”的团队，我们特意还做了一段宣传片：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-08-31&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;iframe src=&quot;&#x2F;&#x2F;player.bilibili.com&#x2F;player.html?isOutside=true&amp;aid=115126497714146&amp;bvid=BV1a9ajzAEkt&amp;cid=32082625098&amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot; height=&quot;400px&quot; width=&quot;100%&quot;&gt;&lt;&#x2F;iframe&gt;
&lt;p&gt;另外，这款游戏实际上是可以与其他人对战的。原理就是将玩家A的每秒的操作并记录下来，玩家B在“挑战”玩家A时，就是直接把玩家A的数据放到内存播放出来，玩家B就能与之对战了，现在想来也是有趣。还有一个值得吐槽的点就是源代码没有封装的概念，只要涉及到数据库查询的地方，都完整的编写一段数据库建立连接、创建Sql读写的逻辑，很是原始。话说，这玩意儿当时还在某通动力的软件大赛上拿了个一等奖，也是比较魔幻了。&lt;&#x2F;p&gt;
&lt;p&gt;关于大学的事儿，就暂时想到了这些，还有什么要分享的，等有时间了再更新吧。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fu-lu&quot;&gt;附录&lt;&#x2F;h2&gt;
&lt;p&gt;上面提到的项目，我也传到了Github上，仅做纪念：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;ttms&quot;&gt;w4ngzhen&#x2F;ttms&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;HorseRacing&quot;&gt;w4ngzhen&#x2F;HorseRacing&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>COME，轻量级自托管的站点评论系统套件</title>
        <published>2025-07-31T00:00:00+00:00</published>
        <updated>2025-07-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/COME，轻量级自托管的站点评论系统套件/"/>
        <id>https://zhen.wang/article/COME，轻量级自托管的站点评论系统套件/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/COME，轻量级自托管的站点评论系统套件/">&lt;p&gt;常看笔者博文的读者都知道，笔者的博客站点（https:&#x2F;&#x2F;zhen.wang）为了其简约性，一直以来都未直接集成评论能力，而是通过让读者通过跳转 GitHub 并提交 issue 方式来收集读者反馈。虽然这种方式可行，但实际上一点也不便捷。本月初的时候，收到一名读者的留言，希望对博客留言的方式进行优化。在对各种留言评论系统进行调研以后，笔者还是决定再造一造轮子。于是，笔者花了几天时间开发了 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;come&quot;&gt;COME&lt;&#x2F;a&gt;，一套能够自托管的，足够轻量的评论系统。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ji-ben-neng-li&quot;&gt;基本能力&lt;&#x2F;h1&gt;
&lt;p&gt;COME主要有两个特点：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;自托管&lt;&#x2F;li&gt;
&lt;li&gt;足够轻量&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;对于“自托管”来说，本系统主要借助Cloudflare提供的能力&#x2F;服务，通过免费（一定额度）的 Worker 来承载评论系统的后台服务接口逻辑，通过 Cloudflare D1 轻量级数据库来存储所有的评论数据。&lt;&#x2F;p&gt;
&lt;p&gt;对于“足够轻量”来说，笔者在设计本系统时，整个系统只使用了&lt;strong&gt;一张&lt;&#x2F;strong&gt;数据库表来存储站点评论数据，不引入其他额外的复杂功能来增加本系统复杂度，以及开发心智负担，我相信任何感兴趣的朋友可以快速理解本系统，并搭建一套属于自己的评论系统。&lt;&#x2F;p&gt;
&lt;p&gt;就目前而言，本系统包含了以下功能：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;一个基于包含preact运行时的轻量级客户端评论展现&#x2F;提交组件（umd形式提供），&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;come&#x2F;tree&#x2F;main&#x2F;packages&#x2F;web-comment-box&quot;&gt;ComeCommentBox&lt;&#x2F;a&gt;。该组件使用 TypeScript + preact 进行开发，得益于 preact 的足够轻量级，ComeCommentBox 最终构建后产物（js+css）文件体积在 37 KB左右（包含preact运行时），通过简单的配置，可以轻松的嵌入挂载到任意Web页面上，加载并渲染相关的评论列表。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;评论后台管理服务（基于Hono开发），以及后台管理Web页面（TypeScript + React + antd@5，&lt;strong&gt;开发中&lt;&#x2F;strong&gt;），用于管理留言评论：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;评论审核（默认开启）。开启后，所有来自网友提交评论需管理者主动审核才能展现在客户端页面评论区。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;评论移除。支持管理者手动删除某些留言评论。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;yi-xie-ji-shu-xi-jie&quot;&gt;一些技术细节&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;ping-lun-zhe-shen-fen-ren-zheng-wen-ti&quot;&gt;评论者身份认证问题&lt;&#x2F;h2&gt;
&lt;p&gt;为了尽可能的轻量，&lt;strong&gt;本系统不支持评论者登录能力&lt;&#x2F;strong&gt;（即没有身份验证）。评论者只需提供邮箱、昵称以及评论内容即可完成评论提交。对于用户邮箱地址隐私问题，后台服务不会存储真实的邮箱地址，而是在处理评论提交请求时，对邮箱地址进行脱敏操作（例如将 &lt;code&gt;&quot;abc@xxx.com&quot;&lt;&#x2F;code&gt; 脱敏为 &lt;code&gt;&quot;a***c@xxx.com&quot;&lt;&#x2F;code&gt;），并只存储脱敏邮箱地址；当然，为了确定用户“唯一性”，后台服务会使用通过MD5算法计算邮箱地址的摘要字符串作为用户id进行存储，以便识别“唯一”用户。至于有心之人冒充评论者的问题（比如用户A从别处知道了用户B的邮箱地址以及其评论昵称，进而在评论区冒充用户B发言），本系统暂不考虑应对措施，可交给使用本系统的开发者用户自行拓展能力。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;当然，为了评论区的合法性，笔者强烈建议开启评论审核能力，主动规避剔除一些垃圾评论。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;comecang-ku-jie-du&quot;&gt;COME仓库解读&lt;&#x2F;h2&gt;
&lt;p&gt;COME使用pnpm monorepo方式组织管理项目代码。该仓库下主要包含以下几个模块：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;公共类型定义（packages&#x2F;common-types目录）。该模块仅提供TypeScript类型定义，方便后台服务、前端页面组件共享类型定义。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;评论系统接口服务（packages&#x2F;server目录）。基于Cloudflare Woker的node服务，使用TypeScript开发，整个服务目前只有3个依赖库：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Hono：与Cloudflare深度集成的超轻量级 Web 框架。高性能，零依赖。&lt;&#x2F;li&gt;
&lt;li&gt;drizzle-orm：Cloudflare 生态中用于类型安全数据库操作的轻量级 TypeScript ORM 库，尤其与 Cloudflare D1（边缘 SQLite 数据库）深度集成&lt;&#x2F;li&gt;
&lt;li&gt;zod：一个高性能，轻量级的校验库。可以方便定义一些数据的字段校验规则。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;后台服务管理WebUI（packages&#x2F;web-server-management）。后台服务的Web页面，核心使用React+antd@5开发。通过该Web系统，可以方便的管理站点系统的所有评论留言。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;客户端评论UI组件 ComeCommentBox（packages&#x2F;come-comment-box）。TypeScript + preact 进行开发，只依赖preact。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;geng-duo-nei-rong&quot;&gt;更多内容&lt;&#x2F;h1&gt;
&lt;p&gt;COME的开发虽然快接近尾声，但由于本月笔者事务繁多，时间有限，COME还在有一些事项未彻底完成。笔者计划8月底之前完成COME系统套件的研发收尾、文档编写（包含功能介绍，开发调试）工作。届时发布相关的动态，以及再次更新本文内容，还请各位读者多多关注！&lt;&#x2F;p&gt;
&lt;h2 id=&quot;comecang-ku&quot;&gt;COME仓库&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;come&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;come&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>从零开发Vim-like编辑器（02）探讨编辑器对文本的解析与呈现设计思路</title>
        <published>2025-06-30T00:00:00+00:00</published>
        <updated>2025-06-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从零开发Vim-like编辑器（02）探讨编辑器对文本的解析与呈现设计思路/"/>
        <id>https://zhen.wang/article/从零开发Vim-like编辑器（02）探讨编辑器对文本的解析与呈现设计思路/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/从零开发Vim-like编辑器（02）探讨编辑器对文本的解析与呈现设计思路/">&lt;blockquote&gt;
&lt;p&gt;本文同步发布在我的个人博客：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zhen.wang&quot;&gt;https:&#x2F;&#x2F;zhen.wang&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;前一篇文章作为开篇，只是介绍了Ratatui的相关使用，引出了一些概念。从本文开始，我们正式进入咱们的Vim-like编辑器的开发设计。&lt;&#x2F;p&gt;
&lt;p&gt;Vim-like编辑器，或者说任意类型的文本编辑器，其核心功能无外乎两个：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;呈现当前文本内容。&lt;&#x2F;li&gt;
&lt;li&gt;响应用户输入，修改呈现的文本内容。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;我们本章首先来探讨关于文本解析与呈现。能够呈现一段文本内容的前提是我们得&lt;strong&gt;持有&lt;&#x2F;strong&gt;一段文本，而这这块就涉及到在程序运行中我们应该如何存储文本数据。假设现在存在如下2行的文本内容：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;hello.
&lt;&#x2F;span&gt;&lt;span&gt;你好。
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在Rust中，我们可以使用如下的几种方式来表达这段文本：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 1. 保留换行符的字符串
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; text1: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;hello.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\n&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;你好。&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 2. 每一行一项组成的动态数组，共2行
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; text2: Vec&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; = vec![&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;hello.&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;世界。&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;];
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 3. 每一行由n个字符构成的动态数组，共2行
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; text3: Vec&amp;lt;Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;char&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt; = vec![];
&lt;&#x2F;span&gt;&lt;span&gt;text3.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(vec![&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;h&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;l&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;l&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;o&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;.&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;]);
&lt;&#x2F;span&gt;&lt;span&gt;text3.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;push&lt;&#x2F;span&gt;&lt;span&gt;(vec![&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;你&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;好&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;。&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;]);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;可以看到的是，同一段文本可以有多种数据表达的方式。那我们在实际应用中，应该选择哪种存储方式呢？考虑到在本Vim-like编辑器的设计与开发过程中，我们希望能够精确的控制每一个字符的渲染，目前来看&lt;strong&gt;最简单粗暴的&lt;&#x2F;strong&gt;，就是以方式3来存储原始内容。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;本人没有深入学习过关于文本编辑器的设计模式，从理论上来讲应该还有其他更加优雅且高效的内容存储数据结构，但是考虑到本系列文章的入门性，我们不过多讨论更加高深的内容。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;基于此，我们暂时将存储文本数据的数据结构定义如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span&gt;Content {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;lines&lt;&#x2F;span&gt;&lt;span&gt;: Vec&amp;lt;Vec&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;char&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;line_feed&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;String&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;注意到，除了存储每一行每一个字符的&lt;code&gt;Vec&amp;lt;Vec&amp;lt;char&amp;gt;&amp;gt;&lt;&#x2F;code&gt;字段&lt;code&gt;lines&lt;&#x2F;code&gt;以外，我们还定了一个名为&lt;code&gt;line_feed&lt;&#x2F;code&gt;的字段，这个字段主要是为了存储换行符。定义这个字段，是因为我们在后续将输入的包含多行文本内容的数据处理后，多行文本已经被分解为了&lt;code&gt;Vec&amp;lt;Vec&amp;lt;char&amp;gt;&amp;gt;&lt;&#x2F;code&gt;，不再有换行符的存在，因此我们需要将识别到的换行符存储下来，在某些场景需要用到换行符时使用（比如写入到文件时）。&lt;&#x2F;p&gt;
&lt;p&gt;接下来，让我们讨论一下关于使用&lt;code&gt;Vec&amp;lt;Vec&amp;lt;char&amp;gt;&amp;gt;&lt;&#x2F;code&gt;存储文本数据的一些细节。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;wen-ben-jie-xi-yu-cun-chu&quot;&gt;文本解析与存储&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;guan-yu-huan-xing-fu-de-li-jie&quot;&gt;关于换行符的理解&lt;&#x2F;h2&gt;
&lt;p&gt;当我们读取已存在的文本文件时，面临的第一个问题是如何理解“换行”，因为文本内容最终加载到程序内存中后，并没有所谓的视觉表现上的换行，在内存中它是一段连续一维的字符序列，只不过在某些位置存在“换行标记”：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; 文本内容
&lt;&#x2F;span&gt;&lt;span&gt;hi!
&lt;&#x2F;span&gt;&lt;span&gt;世界。
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&#x2F; 读取到内存中实际上是一维字符序列
&lt;&#x2F;span&gt;&lt;span&gt;[&amp;#39;h&amp;#39;, &amp;#39;i&amp;#39;, &amp;#39;!&amp;#39;, &amp;#39;换行标记&amp;#39;, &amp;#39;世&amp;#39;, &amp;#39;界&amp;#39;, &amp;#39;。&amp;#39;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;当然提起换行符，在网上我们经常会看到 这样一句话：“Windows使用CRLF（&lt;code&gt;\r\n&lt;&#x2F;code&gt;）双字符组合作为换行符，而macOS从OS X开始已经与Linux统一采用LF（&lt;code&gt;\n&lt;&#x2F;code&gt;）单字符。”，在笔者看来这句话非常有误导性，给人的感觉是Windows上的文本数据似乎&lt;strong&gt;都是&lt;&#x2F;strong&gt;&lt;code&gt;\r\n&lt;&#x2F;code&gt;作为换行符，而对于macOS&#x2F;Linux&#x2F;Unix上，则似乎&lt;strong&gt;都是&lt;&#x2F;strong&gt;使用&lt;code&gt;\n&lt;&#x2F;code&gt;作为换行符。但实际上，这块应该取决于在操作系统之上的上层应用如何定义“换行”，有的软件会识别&lt;code&gt;\r\n&lt;&#x2F;code&gt;双字符组合作为换行符，而有的软件会识别&lt;code&gt;\n&lt;&#x2F;code&gt;单字符作为换行符。比如存在如下两个文本序列：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;&#x2F;&#x2F; text1
&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;h&amp;#39;, &amp;#39;i&amp;#39;, &amp;#39;!&amp;#39;, &amp;#39;\r&amp;#39;, &amp;#39;\n&amp;#39;, &amp;#39;世&amp;#39;, &amp;#39;界&amp;#39;, &amp;#39;。&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&#x2F; text2
&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;h&amp;#39;, &amp;#39;i&amp;#39;, &amp;#39;!&amp;#39;, &amp;#39;\n&amp;#39;, &amp;#39;世&amp;#39;, &amp;#39;界&amp;#39;, &amp;#39;。&amp;#39;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如果某个文本编辑器&lt;strong&gt;只能&lt;&#x2F;strong&gt;识别&lt;code&gt;\r\n&lt;&#x2F;code&gt;换行符，&lt;strong&gt;无论这个编辑器是否运行在哪个操作系统上，对于text1它总是能够正确识别到换行&lt;&#x2F;strong&gt;，而对于text2它就无法识别换行，因此最终渲染出来可能就是一行文本（&lt;code&gt;\n&lt;&#x2F;code&gt;可能根据不同的实现不显示或显示为乱码）。因此，“Windows使用CRLF（&lt;code&gt;\r\n&lt;&#x2F;code&gt;）双字符组合作为换行符，而macOS从OS X开始已经与Linux统一采用LF（&lt;code&gt;\n&lt;&#x2F;code&gt;）单字符。”这句话在笔者看来，更为准确的表达应该是：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Windows 系统内核和原生工具（如记事本）&lt;strong&gt;默认&lt;&#x2F;strong&gt;生产和消费 &lt;code&gt;\r\n&lt;&#x2F;code&gt; 换行符。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;macOS&#x2F;Linux&#x2F;Unix 系统内核及工具（如 cat、vi）&lt;strong&gt;默认&lt;&#x2F;strong&gt;生产和消费 &lt;code&gt;\n&lt;&#x2F;code&gt; 换行符。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;从本质上讲，这是操作系统和配套工具链的&lt;strong&gt;默认约定&lt;&#x2F;strong&gt;。当然，如今很多主流编辑器（VS Code、Sublime Text等）都支持对这两种换行符的智能识别和切换。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;huan-xing-chu-li-luo-ji-she-ji&quot;&gt;换行处理逻辑设计&lt;&#x2F;h2&gt;
&lt;p&gt;回到我们自己Vim-like编辑器设计部分，要实现识别给定的文本内容（的换行符）以及转化为前面我们定义的字符动态数组结构（&lt;code&gt;Vec&amp;lt;Vec&amp;lt;char&amp;gt;&amp;gt;&lt;&#x2F;code&gt;），在这里我们可以采用如下逻辑：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;检测一段文本内容的换行符并设置为默认换行符。&lt;&#x2F;li&gt;
&lt;li&gt;解析文本内容，转化char动态数组。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;strong&gt;对于步骤1：检测默认换行符。&lt;strong&gt;我们可以从左到右逐步读入单个字符，检测&lt;&#x2F;strong&gt;首次出现&lt;&#x2F;strong&gt;的换行符（&lt;code&gt;\r\n&lt;&#x2F;code&gt;或&lt;code&gt;\n&lt;&#x2F;code&gt;，或没有换行符），如果存在换行符，则将该换行符作为该段文本内容的默认换行符；如果不存在换行符，则使用系统默认换行符作为该文件的默认换行符（Windows是&lt;code&gt;\r\n&lt;&#x2F;code&gt;，macOS&#x2F;Linux&#x2F;Unix等是&lt;code&gt;\n&lt;&#x2F;code&gt;）。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;识别首次出现换行符的较坏的情况是所识别的文本不包含任意的换行符，程序会遍历这“一行”文本的每一个字符直到最后一个字符，更坏的情况是这“一行”文本还特别长。不过我们暂时不考虑这种超长单行文本的换行效率问题。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;注意，笔者强调这里的逻辑只看&lt;strong&gt;首次出现&lt;&#x2F;strong&gt;的换行符是什么，比如下面的文本，即使后续还有2个&lt;code&gt;\r\n&lt;&#x2F;code&gt;换行符，但我们依然使用首次出现的&lt;code&gt;\n&lt;&#x2F;code&gt;作为该文本内容的默认换行符：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;识别到的默认换行符我们可以存储起来（例如前面的&lt;code&gt;Content&lt;&#x2F;code&gt;的&lt;code&gt;line_feed&lt;&#x2F;code&gt;字段），在编辑器后续使用过程中，在保存多行文本数据写入到文件时，作为插入的换行符号来使用：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;对于步骤2：解析文本内容，转化char动态数组。&lt;strong&gt;我们可以先准备一个&lt;code&gt;Vec&amp;lt;char&amp;gt;&lt;&#x2F;code&gt;临时容器temp_line来准备存放一行的每一个字符；然后从左到右每读取一个char，每次读取到的char只要&lt;&#x2F;strong&gt;不是&lt;&#x2F;strong&gt;&lt;code&gt;\n&lt;&#x2F;code&gt;字符，就将该char放入temp_line中。当&lt;code&gt;\n&lt;&#x2F;code&gt;出现时（这个&lt;code&gt;\n&lt;&#x2F;code&gt;我们不会放入&lt;code&gt;Vec&amp;lt;char&amp;gt;&lt;&#x2F;code&gt;），我们再检查当前temp_line容器里最后一个元素是否是&lt;code&gt;\r&lt;&#x2F;code&gt;，如果是&lt;code&gt;\r&lt;&#x2F;code&gt;，也把它移除（这一步是为处理某一行的换行符是&lt;code&gt;\r\n&lt;&#x2F;code&gt;这种情况），此时剩下的就是目前识别到的该行文本。当然，在最后将所有字符遍历结束后，temp_line还存在一些字符，则说明此时temp_line就是最后一行数据：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这个处理逻辑可以同时将&lt;code&gt;\n&lt;&#x2F;code&gt;和&lt;code&gt;\r\n&lt;&#x2F;code&gt;的场景都考虑并处理掉，且复杂度是&lt;code&gt;O(n)&lt;&#x2F;code&gt;；同时，我们还可以在该处理逻辑中将上面第一步文本默认换行符识别的逻辑兼容到，只需要通过一个标志变量来确定是否是第一次出现换行符即可。最后，对于这块的代码处理代码如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;其中，对于&lt;code&gt;for ch in value.chars()&lt;&#x2F;code&gt;遍历字符的过程如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，这段代码还有一个小小的 &lt;del&gt;BUG&lt;&#x2F;del&gt; 特性，如果该文本的最后一个字符是&lt;code&gt;\n&lt;&#x2F;code&gt;，则等价于没有该换行符，比如：&quot;Hello&lt;code&gt;\n&lt;&#x2F;code&gt;&quot;的处理结果会和&quot;Hello&quot;一行，都会被处理为1行，而不是2行。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;对于上述代码，我们最终可以通过单元测试来验证：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;wen-ben-de-cheng-xian&quot;&gt;文本的呈现&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;ratatuide-wen-ben-xuan-ran&quot;&gt;Ratatui的文本渲染&lt;&#x2F;h2&gt;
&lt;p&gt;⽬前为⽌，我们完成了一个基础的存储文本数据的模型设计。当然，光是存储文本还远远不够，我们最终要完成的是一款Vim-like的文本编辑器，我们需要将文本内容呈现在一个文本编辑器区域。既然是要呈现内容，我们就需要设计一下文本呈现的逻辑。首先，让我们先了解一下Rataui官方的文本渲染。就目前而言，Ratatui官方支持如下的几种文本渲染方式：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Span&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于 &lt;code&gt;Span&lt;&#x2F;code&gt; ，你可以认为它是可以&lt;strong&gt;最小粒度可定制样式&lt;&#x2F;strong&gt;的单元，比如，如下的示例代码中表示了三种形式的 &lt;code&gt;Span&lt;&#x2F;code&gt; 构造以及效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Line&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;其次是 &lt;code&gt;Line&lt;&#x2F;code&gt; 。&lt;code&gt;Line&lt;&#x2F;code&gt; 可以认为是一组 &lt;code&gt;Span&lt;&#x2F;code&gt; 实例的合集，当我们需要将一行文本多个部分各自呈现不同的样式时，就需要将多个部分拆分为不同的 &lt;code&gt;Span&lt;&#x2F;code&gt; ，然后构造 &lt;code&gt;Line&lt;&#x2F;code&gt; 来持有它们：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Text&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;Text&lt;&#x2F;code&gt; 是输出文本的最终构建区块。&lt;code&gt;Text&lt;&#x2F;code&gt; 对象表示 &lt;code&gt;Line&lt;&#x2F;code&gt; 对象实例的一组集合，并且n个 &lt;code&gt;Line&lt;&#x2F;code&gt; 会在UI渲染上呈现n行：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Buffer Cell（底层）&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;除上述几种之外，Ratatui还有一种更加底层的字符渲染。通过获取对应的命令行buffer，在指定位置设置字符：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;110.png&quot; alt=&quot;110&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;官方文档：https:&#x2F;&#x2F;ratatui.rs&#x2F;recipes&#x2F;render&#x2F;display-text&#x2F;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;bian-ji-qi-de-wen-ben-xuan-ran-she-ji&quot;&gt;编辑器的文本渲染设计&lt;&#x2F;h2&gt;
&lt;p&gt;在了解了Ratatui的文本渲染API以后，接下来让我们回到编辑器的文本渲染。简单来看，我们也许可以将一行文本（&lt;code&gt;Vec&amp;lt;char&amp;gt;&lt;&#x2F;code&gt;）直接转换为 &lt;code&gt;Line&lt;&#x2F;code&gt; 实例交给Ratatui进行渲染，但这样的做法会丧失了⼀定的灵活性，原因在于我们的Vim-like编辑器呈现⽂本的能力，在后续的迭代过程中⼤概率需要支持不同⽂字⽚段的能够完成不同颜⾊渲染：：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;120.png&quot; alt=&quot;120&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;像类似上图这样的效果，我们肯定需要将一行文本拆分为多个 &lt;code&gt;Span&lt;&#x2F;code&gt; 实例来控制局部自定义文本其片段样式（比如颜色、下划线等）。因此，我们需要将文本原始数据的存储和渲染进行解耦：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;130.png&quot; alt=&quot;130&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;因此，我们需要⼀个逻辑流程来完成从原始文本到Ratatui中 &lt;code&gt;Span&lt;&#x2F;code&gt; 实例的映射逻辑（例如上图的6个char会映射为4个 &lt;code&gt;Span&lt;&#x2F;code&gt; 实例）。这个映射逻辑的具体细节我们先不着急在这里讲解，读者先有一个思路印象即可。因为在接下来笔者还会补充⼀些额外的细节点，才能让整个映射逻辑的流程更加清晰。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;yuan-shi-zi-fu-yu-renderterm&quot;&gt;原始字符与RenderTerm&lt;&#x2F;h3&gt;
&lt;p&gt;在编辑器中我们能够展示输入的各种文本字符，但有一类字符比较特殊，例如制表符 &lt;code&gt;&#x27;\t&#x27;&lt;&#x2F;code&gt; 。相信有的读者在一些主流编辑器中都见过关于制表符的设置：一个水平tab渲染为2个或4个空格宽度。如果按照字符直接渲染到编辑器上，我们会发现像是制表符这种字符，应该渲染为2个空格宽度的“空白区域”还是4个呢？答案是无法确定，我们应该允许用户进行配置。此外，对于一段来自外部输入的文本内容，我们无法保证里面的任何一个char字符都是可见的。比如，在下面的ascii表中：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;140.png&quot; alt=&quot;140&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;0x00&lt;&#x2F;code&gt; 到 &lt;code&gt;0x1f&lt;&#x2F;code&gt;（上部分红框）的字符以及最后一个 &lt;code&gt;0x7f&lt;&#x2F;code&gt;（&lt;code&gt;DEL&lt;&#x2F;code&gt;删除字符）这类的“控制字符”都有其编码，可以存储在 char 中：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; ch = &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\u{00}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; NUL
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; ch2 = &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\u{07}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; BEL
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; ch3 = &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\u{0a}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; LF
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在笔者的机器上，尝试在控制台输出这些字符的时候，呈现如下效果：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;println!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;456&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;789&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, ch, ch2, ch3);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 下面是在控制台的实际输出效果:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123456789
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;实际输出根据不同的命令行终端会有不同的效果&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在笔者使用的WezTerm终端软件上，可以看到 &quot;123&quot; 与 &quot;456&quot; 之间的 &lt;code&gt;\u{00}&lt;&#x2F;code&gt;（NUL）以及 &quot;456&quot; 与 &quot;789&quot; 之间的 &lt;code&gt;\u{07}&lt;&#x2F;code&gt;（BEL）都没有打印到控制台，但 &quot;789&quot; 与 &quot;0&quot; 之间的 &lt;code&gt;\u{0a}&lt;&#x2F;code&gt;（LF换行符）控制了最后的控制台输出效果，将 &quot;789&quot; 与 &quot;0&quot; 分割为了两行。也就是说，笔者机器上的命令行终端在输出一些不可见字符的时候，没有打印到控制台。&lt;&#x2F;p&gt;
&lt;p&gt;回到咱们的Vim-like编辑器。在继续讨论前，我们先确定一个原则：“&lt;strong&gt;以数据驱动为基本模式，数据与视图始终分离&lt;&#x2F;strong&gt;”。在这个原则的基础上，我们再确定这样一个事实： &lt;code&gt;Vec&amp;lt;Vec&amp;lt;char&amp;gt;&amp;gt; &lt;&#x2F;code&gt; 会存储我们文本中除开换行符以外的所有文本字符（Rust中char是unicode），无论其是否可见，并且，我们不会&lt;strong&gt;擅自&lt;&#x2F;strong&gt;更改这里面的任何一个char数据。&lt;&#x2F;p&gt;
&lt;p&gt;假设存在如下的文本内容：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;[&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;h&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;i&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\u{b}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;(ascii &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0xb&lt;&#x2F;span&gt;&lt;span&gt;), &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;,&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\u{0}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;(ascii &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0x0&lt;&#x2F;span&gt;&lt;span&gt;)]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;对于这一行内容，&lt;code&gt;&#x27;h&#x27;&lt;&#x2F;code&gt; 、&lt;code&gt;&#x27;i&#x27;&lt;&#x2F;code&gt;  、&lt;code&gt;&#x27;,&#x27;&lt;&#x2F;code&gt; 当然可以渲染展示到界面上，但是对于 &lt;code&gt;&#x27;\u{b}&#x27;&lt;&#x2F;code&gt; 以及 &lt;code&gt;&#x27;\u{0}&#x27;&lt;&#x2F;code&gt; 我们应当如何渲染呢？很显然，在原始的文本数据字符到最终渲染到屏幕上的字符无法完全的一一对应，&lt;strong&gt;假设对于本Vim-like编辑器，笔者从主观上考虑设计为如下形式&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;150.png&quot; alt=&quot;150&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;即，我们在渲染某些特殊字符时，会设计成将其渲染为 &lt;code&gt;\u{该字符unicode}&lt;&#x2F;code&gt;。对这种场景更进一步抽象，&lt;strong&gt;我们本质上是希望编辑器支持在准备渲染某个字符时，将其映射为另外形式的文本的能力。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;为了承载这个映射逻辑，笔者引入 &lt;strong&gt;渲染Term（RenderTerm）&lt;&#x2F;strong&gt; 的概念，渲染Term来源于我们文本数据中的某单个字符char，并保存了关于这个字符的一些有用上下文（比如，type表明是否为可见字符，render_text指实际渲染的文本等）。同时，渲染Term在其保存的上下文的基础上，内部经过某些配置、逻辑，能够转化为得最终渲染终端屏幕上的单个 &lt;code&gt;Span&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;160.png&quot; alt=&quot;160&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在有了上述渲染Term，我们今后就可以很容易的来将某些字符进行任意效果的映射，比如我们在面对一个 char &lt;code&gt;&#x27;a&#x27;&lt;&#x2F;code&gt; ，将其渲染为 &lt;code&gt;作者厉害&lt;&#x2F;code&gt; ：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;170.png&quot; alt=&quot;170&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;shi-kou-viewport&quot;&gt;视口Viewport&lt;&#x2F;h3&gt;
&lt;p&gt;⽂本数据是来自外部不确定的内容，但是编辑器本身的⼤⼩是可描述的。命令行终端编辑器本质上是一个矩形区域，我们在实现编辑器时，就需要考虑如何将⽂本正确的渲染到这个区域内。&lt;&#x2F;p&gt;
&lt;p&gt;在此，笔者再引入一个概念：编辑器文本&lt;strong&gt;视口Viewport&lt;&#x2F;strong&gt;，它包含两个核心部分：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;视口尺寸，通常指一个row行col列的矩形区域。&lt;&#x2F;li&gt;
&lt;li&gt;视口锚点，指这个视口的左上角应该位于当前文本的哪个字符位置（通常用&lt;code&gt;(x行, y列)&lt;&#x2F;code&gt;坐标来表示）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;视口主要有两个作用，一是描述实际终端界面上的某个区域；二是“框住”一部分文本，将“框住”部分的文本呈现渲染到这个可视区域内。例如，如下的4行10列的文本（我们先关注所有字符都等宽的情况），在一个视口尺寸为3行3列，视口锚点分别为&lt;code&gt;(0, 0)&lt;&#x2F;code&gt;和&lt;code&gt;(2, 1)&lt;&#x2F;code&gt;的视口区域下，编辑器理论上所呈现的内容如下所示：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;180.png&quot; alt=&quot;180&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;当然，光是纯粹粗暴的“框住”某个区域的文本还不够，因为还涉及到一些细节我们没有考虑到。首先，我们在前面引入了渲染Term，假设有如下原始文本数据：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;[&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;h&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;e&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;\u{b}&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;l&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;l&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;o&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此时我们加载的文本有6个字符，其中包含了5个可视的字符&lt;code&gt;hello&lt;&#x2F;code&gt;以及1个不可见的字符&lt;code&gt;\u{b}&lt;&#x2F;code&gt;，我们在前面已经设计了这里的渲染方式，构造渲染Term，并且对于 &lt;code&gt;\u{b}&lt;&#x2F;code&gt; 的渲染Term，渲染文本使用一个字符串 &lt;code&gt;&quot;\u{b}&quot;&lt;&#x2F;code&gt; 来替代这里的不可见字符。因此，假设命令行的视口宽度远远超过这行文本，那么我们应该会看到如下的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;190.png&quot; alt=&quot;190&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;但是当命令行的视口宽度只有&lt;strong&gt;3&lt;&#x2F;strong&gt;列时，如果我们暴力的按照最终渲染的文本来截取，就会发生如下的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;200.png&quot; alt=&quot;200&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;可以看到，由于我们的视口尺寸的UI宽度只有3个单位，原本渲染到界面上的渲染文本 &lt;code&gt;&quot;he\u{b}llo&quot;&lt;&#x2F;code&gt; 一共会占据10个宽度单位，于是最终被渲染的第三个渲染Term（render_text = &lt;code&gt;&quot;\u{b}&quot;&lt;&#x2F;code&gt;）被截取了。为了避免这样的问题，我们需要这样的逻辑：判断每个字符的渲染Term的渲染文本，所对应的渲染文本只有能被完整的渲染视口中，这个渲染Term才能最终转换为 &lt;code&gt;Span&lt;&#x2F;code&gt; 交给 Ratatui 渲染。&lt;&#x2F;p&gt;
&lt;h3 id=&quot;cjkzi-fu-xuan-ran-zhu-yi-dian&quot;&gt;CJK字符渲染注意点&lt;&#x2F;h3&gt;
&lt;p&gt;在Ratatui中，渲染CJK字符（例如中文字符）比起渲染一个常规的ascii字符（&#x27;a&#x27;, &#x27;b&#x27;, &#x27;c&#x27;, ..., &#x27;1&#x27;, &#x27;2&#x27;, ...&#x27;）有更多的细节需要考虑知晓。假设有如下的一段4个中文字符的文本：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;[&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;你&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;好&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;中&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;, &amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;文&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;]
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这些中文字符在终端中占据的UI宽度单位与我们常规的ascii字符的宽度不一样。先给出一个结论，普通的ascii字符在命令行终端只占据1个单位宽度，而中文字符则会占用2个单位宽度。当然，这并不是Ratatui随意制定的规则，而是依照一套Unicode字符在命令行终端渲染的规范。首先，Ratatui内部使用&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;unicode-width&#x2F;latest&#x2F;unicode_width&#x2F;&quot;&gt;unicode-width&lt;&#x2F;a&gt;这个库来确定一个字符的宽度，而该库的核心作用是根据&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.unicode.org&#x2F;reports&#x2F;tr11&#x2F;&quot;&gt;《Unicode标准规范附录11》&lt;&#x2F;a&gt;的要求来返回一个字符在命令行中应该占据的宽度。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Unicode® Standard Annex #11（UAX #11）是Unicode技术标准中的一个重要附件，全称为**《East Asian Width》**（东亚宽度）。它定义了东亚文字（如中文、日文、韩文）在终端显示时的宽度属性规范，主要解决字符在等宽字体环境下的对齐和布局问题。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;因此，当我们使用Ratatui来打印上述的中文字符，并和⼀些常规字符进⾏对⽐，就会发现中文字符确实占据是常规ascii字符的两倍宽度：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;210.png&quot; alt=&quot;210&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;那么，在命令行渲染下，中文等东亚文字所占据的宽度与常规ascii字符存在差异会造成什么影响呢？相信读者已经想到了，这种情况和前面提到的渲染Term的实际渲染文本无法在指定宽度内完整显示本质上是一样的。假设我们使用一个3x3的视口，去渲染如下文本，理论上第2个中文字符 &quot;好&quot; 从UI效果上就会出现截断：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;220.png&quot; alt=&quot;220&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;不过，由于Ratatui内部已经对这块的细节考虑了，因此，实际在如下的3x3矩形区域去渲染文本时，Ratatui会帮我们把无法完整渲染的文本给剔除掉：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;230.png&quot; alt=&quot;230&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;然而，**在实现我们自己的编辑器的时候，我们还是得需要自己去做这块的检测机制。**因为我们先前设计了渲染Term这一数据模型来实现将某个char映射另外的文本，对于这些映射后的文本，它们本身对于Ratatui来说是就是正常的文本（例如映射后的 &quot;\u{0}&quot; ，会占据5个单位 ，如果不加检测，就会被错误的截断。为了统一处理逻辑，我们也将中文字符的渲染收口到渲染Term：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-06-30&#x2F;240.png&quot; alt=&quot;240&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;也就是说，对于任意一个字符，我们都将起包装为渲染Term，该渲染Term能够返回最终渲染的文本的Unicode命令行宽度，以便于我们后续根据视口来合理的进行水平裁剪。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;讲到这里，本章内容就差不多了。细心的读者可能发现，相较与之前的文章，本文并没有平铺太多纯代码，因为笔者现在写作时考虑更多的是将一些设计思路表达出来，而不是使用太多的代码来水文章内容。这样做一方面可以让文章的内容更加饱满，另一方面读者也可根据本文的思路自己去代码实践。&lt;&#x2F;p&gt;
&lt;p&gt;当然，本章暂时还没有提到如何设计一个几乎所有编辑器都有的功能：&lt;strong&gt;视觉换行&lt;&#x2F;strong&gt;。这个功能主要用于当一行文本超过视口宽度时，超过的部分换行展示到下一行。值得注意的是，这个处理只是UI视觉上的换行，而不是对文本内容插入了真实的换行符进行换行。由于这块内容较为复杂，且本章内容已经比较多了，因此笔者将这块放到下一章进行讲解。读者可以根据本章内容先消化消化，以便在后续的章节中更好的理解设计思路。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>从零开发Vim-like编辑器（01）起步</title>
        <published>2025-05-28T00:00:00+00:00</published>
        <updated>2025-05-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从零开发Vim-like编辑器（01）起步/"/>
        <id>https://zhen.wang/article/从零开发Vim-like编辑器（01）起步/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/从零开发Vim-like编辑器（01）起步/">&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;Vim和Neovim因其独特的模态编辑和高度可定制化，被列为程序员常用的文本编辑器选项之一，与Sublime Text、VS Code、Emacs等编辑器共同丰富了开发者工具生态。就目前而言，网络上绝大多数的文章都在讲解如何为Vim、Neovim编写配置，更深入一点的文章会教大家如何开发相关的插件。但作为开发者的你是否有想过，Vim、Neovim这样amazing的编辑器是如何开发出来的，甚至更大胆一点，你是否有想过能否自己编写一款类Vim编辑器呢？&lt;&#x2F;p&gt;
&lt;p&gt;本系列文章笔者将带你从零开始，基于Vim现有的功能，拆解模态编辑的实现原理，使用Rust一步步开发构建一个具备Vim基本特性的轻量级编辑器。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;qian-zhi-zhun-bei&quot;&gt;前置准备&lt;&#x2F;h1&gt;
&lt;p&gt;&quot;工欲善其事，必先利其器&quot;。&lt;&#x2F;p&gt;
&lt;p&gt;本系列文章所开发的Vim-like编辑器，将会运行在命令行中，以TUI的形式进行呈现。在代码编写的过程中，我们将会使用&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ratatui.rs&#x2F;&quot;&gt;Rataui&lt;&#x2F;a&gt;这个三方库来作为最基础的TUI渲染框架。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Ratatui是一个Rust库，用于烹饪美味的TUI（终端用户界面）。它是一个轻量级库，提供了一组小部件和实用程序来构建简单或复杂的Rust TUI。作为TUI构建库，Ratatui也内建了一些&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ratatui.rs&#x2F;showcase&#x2F;widgets&#x2F;&quot;&gt;基础的 UI 组件&lt;&#x2F;a&gt;，同时社区也有许多基于Ratatui开发的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ratatui.rs&#x2F;showcase&#x2F;third-party-widgets&#x2F;&quot;&gt;第三方 UI 组件&lt;&#x2F;a&gt;，上手会比较快。&lt;&#x2F;p&gt;
&lt;p&gt;考虑到本文读者不一定都了解这个库，所以笔者在接下来将会简单介绍Ratatui的基本概念和基础使用，让读者对该库有一个直观的感受。同时，在后续文章介绍过程中页会根据实际的情况适当的介绍Ratatui的一些深入的内容，但考虑文章重点，不会过多讲解（不然就成了《从零开始使用Ratatui》了），因此还请笔者自行通过官方的教程学习Ratatui的使用。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ratatuide-ji-ben-shi-yong&quot;&gt;ratatui的基本使用&lt;&#x2F;h2&gt;
&lt;p&gt;我们首先通过官方的一个基础例子，来理解ratatui的使用思路：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这个例子中最核心部分的莫过于我们在一个&lt;code&gt;loop&lt;&#x2F;code&gt;循环迭代中，先调用终端（terminal）实例的&lt;code&gt;draw&lt;&#x2F;code&gt;方法，完成对 UI 组件的绘制，紧接着读取事件做出对应按键的逻辑响应：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;其中的绘制具体流程为：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;执行终端实例的&lt;code&gt;draw&lt;&#x2F;code&gt;方法，传递一个方法回调，该方法入参是一个帧（Frame）实例，代表了每一次绘制的上下文&lt;&#x2F;li&gt;
&lt;li&gt;调用帧实例的&lt;code&gt;area&lt;&#x2F;code&gt;方法获取终端为当前帧分配的的区域（Rect）&lt;&#x2F;li&gt;
&lt;li&gt;调用帧实例的&lt;code&gt;draw&lt;&#x2F;code&gt;方法，将 UI 组件绘制到指定区域中&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;绘制环节完成后，我们调用&lt;code&gt;ratatui::crossterm::event::read()&lt;&#x2F;code&gt;来获取捕获到的事件消息，并针对这些事件的具体分类执行对应逻辑。&lt;&#x2F;p&gt;
&lt;p&gt;读者可能很疑惑，这里的&lt;code&gt;ratatui::crossterm&lt;&#x2F;code&gt;是什么？要解释这个问题，我们要理解一个事实：ratatui是一个TUI渲染引擎，它对实际运行的命令行环境进行了抽象，在内容绘制层面提供了统一的API，而绘制的具体细节逻辑在其内部，通过调用实际的后端（backend）实现，进而调用底层不同的绘制逻辑。当我们调用&lt;code&gt;ratatui::init()&lt;&#x2F;code&gt;时，得到的是一个&lt;strong&gt;默认终端实例&lt;&#x2F;strong&gt;（DefaultTerminal），该默认终端实例在ratatui内部使用的是&lt;code&gt;crossterm&lt;&#x2F;code&gt;这个底层后端，因此，我们相对应的，需要调用ratatui内置默认依赖的&lt;code&gt;crossterm&lt;&#x2F;code&gt;的相关API来读取事件消息。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ratatuide-si-wei-mo-xing&quot;&gt;ratatui的思维模型&lt;&#x2F;h2&gt;
&lt;p&gt;值得介绍的是，ratatui的核心采取的是&lt;strong&gt;立即模式渲染（Immediate Mode Rendering）&lt;&#x2F;strong&gt;，立即模式渲染是一种 UI 范例，其中每帧都会重新创建 UI。与之相对应的是保留模式渲染（Retained Mode Rendering），该模式下通常会创建一组固定的 UI 组件，并在后续的某些时刻更新其状态以达到预期的UI效果。简单来讲：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;保留模式（Retained Mode）：程序只需进行一次的 UI 组件创建，在随后过程中修改这些组件的其属性或处理组件触发的事件。&lt;&#x2F;li&gt;
&lt;li&gt;即时模式（Immediate Mode）：程序根据应用程序&lt;strong&gt;状态&lt;&#x2F;strong&gt;每帧重新绘制 UI，UI只是对状态的渲染表达，是一个瞬时的对象。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;关于这块更多的内容，请读者自行阅读相关的文章进行了解&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;现在，让我们再通过一个例子来理解立即模式渲染思维。假设我们要在屏幕上渲染一个光标（cursor），这个光标可以随着我们的上下左右按键移动：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;060.gif&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于保留模式渲染来说，我们通常会这样做：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个光标组件实例&lt;&#x2F;li&gt;
&lt;li&gt;将光标实例添加到界面上&lt;&#x2F;li&gt;
&lt;li&gt;响应按键事件，修改光标的位置&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; cursor = 创建光标(); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 创建“光标”实例
&lt;&#x2F;span&gt;&lt;span&gt;application.屏幕.添加(cursor); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 将“光标”添加到屏幕实例上
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 注册全局按键事件：键盘右方向键按下时，光标位置右移一个单位
&lt;&#x2F;span&gt;&lt;span&gt;application.onRightKeyPress = () =&amp;gt; cursor.position.x += &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;application.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;(); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 运行程序
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;而如果是立即渲染模式的架构，cursor仅仅是具有状态的数据，我们每一次循环，都是将光标的状态读取出来，调用应用的绘图API，在对应的位置上画一个“光标”：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; cursor = { x: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, y: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0 &lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;loop &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;draw_at&lt;&#x2F;span&gt;&lt;span&gt;(cursor.x, cursor.y, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 每一帧读取光标的位置，在对应位置上绘制
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;(存在右方向键按下) {
&lt;&#x2F;span&gt;&lt;span&gt;        cursor.x += &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;ratatuide-jin-yi-bu-shi-jian&quot;&gt;ratatui的进一步实践&lt;&#x2F;h2&gt;
&lt;p&gt;为了巩固这块的内容，让我们再实现一个稍微复杂的例子：渲染一个光标，我们可以通过按下空格键来切换是否激活该光标；非激活态的光标呈现白色，且不会响应方向键，激活态光标呈现蓝色，且能够根据方向键进行上下左右移动；无论是否激活，我们总是可以通过按下&lt;code&gt;q&lt;&#x2F;code&gt;键来退出应用。&lt;&#x2F;p&gt;
&lt;p&gt;开发ratatui应用时，最重要的一步是要合理的梳理出应用中所涉及的状态。比如，为了实现上述效果，我们首先梳理出应用所具有的状态：光标的位置（position）、颜色（color）、是否激活（activated）。因此，我们编写如下的Rust结构体来描述上述的状态：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;Cursor {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u16&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;y&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u16&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;activated&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 注意，因为光标的颜色与是否激活有关，这里我们简化为只记录是否激活即可
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;接下来，让我们按照三步走：初始化状态；根据状态绘制UI；响应事件修改状态。整体如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于第一步初始化过程很简单：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; cursor = Cursor {
&lt;&#x2F;span&gt;&lt;span&gt;    x: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    y: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    activated: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;false&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;对于第二步根据状态绘制UI，这里我们在循环中编写如下代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;terminal.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;draw&lt;&#x2F;span&gt;&lt;span&gt;(|&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; Frame| {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 先渲染一个灰色背景
&lt;&#x2F;span&gt;&lt;span&gt;    f.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;render_widget&lt;&#x2F;span&gt;&lt;span&gt;(Block::default().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;bg&lt;&#x2F;span&gt;&lt;span&gt;(Color::Gray), f.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;area&lt;&#x2F;span&gt;&lt;span&gt;());
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 在对应位置渲染光标
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; color = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; cursor.activated {
&lt;&#x2F;span&gt;&lt;span&gt;        Color::Blue
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        Color::White
&lt;&#x2F;span&gt;&lt;span&gt;    };
&lt;&#x2F;span&gt;&lt;span&gt;    f.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;buffer_mut&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;cell_mut&lt;&#x2F;span&gt;&lt;span&gt;(Position::new(cursor.x, cursor.y))
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_style&lt;&#x2F;span&gt;&lt;span&gt;(Style::default().&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fg&lt;&#x2F;span&gt;&lt;span&gt;(color).&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;bg&lt;&#x2F;span&gt;&lt;span&gt;(color));
&lt;&#x2F;span&gt;&lt;span&gt;})?;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;该过程的具体逻辑为：我们首先调用Frame的render_widget方法在整个命令行界面绘制了一个灰色区块（占满了整个屏幕）；接着，根据当前光标的激活状态（&lt;code&gt;activated&lt;&#x2F;code&gt;）来计算出接下来要渲染的光标的颜色；最后，调用Frame的API来获取命令行屏幕缓冲区的某个位置块（Cell），在这个Cell中，我们可以填入了前面计算而来的颜色。&lt;&#x2F;p&gt;
&lt;p&gt;第三步响应事件修改状态的代码如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; event = event::read()?;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; event {
&lt;&#x2F;span&gt;&lt;span&gt;    Event::Key(KeyEvent { code, .. }) =&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; activated = cursor.activated;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;KeyCode::Char(&amp;#39;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;q&lt;&#x2F;span&gt;&lt;span&gt;&amp;#39;) == code {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;break &lt;&#x2F;span&gt;&lt;span&gt;Ok(());
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else if &lt;&#x2F;span&gt;&lt;span&gt;KeyCode::Char(&amp;#39; &amp;#39;) == code {
&lt;&#x2F;span&gt;&lt;span&gt;            cursor.activated = !activated;
&lt;&#x2F;span&gt;&lt;span&gt;        } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else if&lt;&#x2F;span&gt;&lt;span&gt; activated {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;match&lt;&#x2F;span&gt;&lt;span&gt; code {
&lt;&#x2F;span&gt;&lt;span&gt;                KeyCode::Up =&amp;gt; cursor.y = cursor.y.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;saturating_sub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                KeyCode::Down =&amp;gt; cursor.y = cursor.y.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;saturating_add&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                KeyCode::Left =&amp;gt; cursor.x = cursor.x.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;saturating_sub&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                KeyCode::Right =&amp;gt; cursor.x = cursor.x.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;saturating_add&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                _ =&amp;gt; {}
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    _ =&amp;gt; {}
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;上述代码中，调用的saturating_add、saturating_sub是Rust所提供的安全的加减值操作，避免数字因为其字节长度造成溢出。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在上述代码中，我们通过响应空格键（&lt;code&gt;KeyCode::Char(&#x27; &#x27;)&lt;&#x2F;code&gt;）来切换光标的激活状态；通过响应&lt;code&gt;q&lt;&#x2F;code&gt;键退出循环；通过上下左右按键来控制光标的位置。&lt;&#x2F;p&gt;
&lt;p&gt;至此，我们完成了上述例子的代码编写，运行该应用，我们可以看到对应的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-05-28&#x2F;080.gif&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;该节实践代码已上传至 github gist：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gist.github.com&#x2F;w4ngzhen&#x2F;96e49b5054054cab2138c976adf8c98d#file-main-rs&quot;&gt;https:&#x2F;&#x2F;gist.github.com&#x2F;w4ngzhen&#x2F;96e49b5054054cab2138c976adf8c98d#file-main-rs&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本文作为本系列的开篇，还没开始涉及到vim-like编辑器的设计，主要是介绍ratatui这个库的基本的思路概念和一些基本使用。当然，就上述的内容还远远无法胜任接下来的文章，因此笔者希望读者能够仔细阅读&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ratatui.rs&#x2F;concepts&#x2F;&quot;&gt;官方的文档&lt;&#x2F;a&gt;，掌握ratatui的更多内容，这将有助于更好地理解后续文章内容。&lt;&#x2F;p&gt;
&lt;p&gt;在下一篇文章，笔者将会正式搭建项目，并将详细介绍想要实现一款Vim-like编辑器所必要的一些模型设计。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>关于Bevy中的原型Archetypes</title>
        <published>2025-04-26T00:00:00+00:00</published>
        <updated>2025-04-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/关于Bevy中的原型Archetypes/"/>
        <id>https://zhen.wang/article/关于Bevy中的原型Archetypes/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/关于Bevy中的原型Archetypes/">&lt;h1 id=&quot;ren-shi-bevyzhong-de-yuan-xing&quot;&gt;认识Bevy中的原型&lt;&#x2F;h1&gt;
&lt;p&gt;Bevy是基于ECS（Entity-Component-System）架构的游戏引擎，其中的&lt;strong&gt;Entity实体&lt;&#x2F;strong&gt;是游戏中的一个基本对象，但实体本身通常只是一个标识id，它不包含任何具体的数据或行为，&lt;strong&gt;只是组件（Component）的容器&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;具体一点，要在Bevy（或是绝大多数基于ECS架构的游戏引擎）的游戏世界中去创建一个实体，其做法通常是创建一个包含一组组件的合集，该组件合集从概念上来表达某种游戏实体。&lt;&#x2F;p&gt;
&lt;p&gt;例如，创建一个包含生命值（Health）、位置（Position）的敌人角色，我们并不会定义一个名为Enemy的类型去继承某些对象，为其添加生命值数据和位置数据，而是定义3个组件：Enemy、Health以及Position，他们都是组件，当我们同时生成一个包含这3个组件的合集的时候，从概念上它就成为了一个包含生命值以及位置的敌人角色实体：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;针对上述实体的表现形式，从底层存储的角度来看，我们可以先假设通过&lt;strong&gt;表格table&lt;&#x2F;strong&gt;的每一行记录来表达实体：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;上图表示游戏世界中的两个敌人实体，每个实体包含独立的&lt;code&gt;Position&lt;&#x2F;code&gt;和&lt;code&gt;Health&lt;&#x2F;code&gt;组件数据。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;接下来让我们加入一个“玩家”角色，我们首先需要新增一个Player组件，然后在游戏世界中创建包含Player、Postion以及Health三个组件的合集，该实体从概念上就视为一个“玩家”角色，同时按照之前表格的数据表现形式，我们需要给对应的表格再增加一列Player，游戏世界对应的实体情况如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;我们可以很显然想到上述table存在一个问题：随着后续实体增多，这些实体包含的组件千变万化，会造成这个table表格每一行记录是不连续的，且列会越来越多。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;此时如果我们想写向量化的代码来算法优化处理这些数组记录，那么上述结构中不连续处的空值将会影响其效果。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;向量化编程意味着一次对整个数组或数据矢量进行操作的代码，而不是顺序处理单个元素。&lt;&#x2F;p&gt;
&lt;p&gt;向量化操作可以使用像&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zh.wikipedia.org&#x2F;wiki&#x2F;%E5%8D%95%E6%8C%87%E4%BB%A4%E6%B5%81%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%B5%81&quot;&gt;SIMD（单指令流多数据流）&lt;&#x2F;a&gt;这样的硬件优化来有效地执行。这些指令允许在多个数据元素上同时执行相同的操作，通常会提高性能。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;为了解决上述的数组元素不连续的问题，Bevy将包含不同组件的实体拆分到不同的记录表中：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;可以看到，原本包含所有实体记录的单个table拆分为&lt;strong&gt;两种&lt;&#x2F;strong&gt;table，同时其每一行记录是连续的。在Bevy中，会将这两种表（组件类型构成）视为两种&lt;strong&gt;原型（Archetype）&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chai-fen-wei-duo-ge-yuan-xing-de-you-shi&quot;&gt;拆分为多个原型的优势&lt;&#x2F;h2&gt;
&lt;p&gt;读者可能会好奇这样做拆分的目的是什么。实际上，拆分目的是能够充分利用并行计算，在Bevy内部同时处理不同的系统。假设现在有如下两个系统（system）：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle_player_position&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;player_positions&lt;&#x2F;span&gt;&lt;span&gt;: Query&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; Position, With&amp;lt;Player&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;handle_not_player_position&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;not_player_positions&lt;&#x2F;span&gt;&lt;span&gt;: Query&amp;lt;&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; Position, Without&amp;lt;Player&amp;gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;从系统1&lt;code&gt;handle_player_position&lt;&#x2F;code&gt;的query参数我们可以知道，运行该系统时，我们会处理&lt;strong&gt;具有&lt;&#x2F;strong&gt;Player组件的实体的位置数据，而从系统2&lt;code&gt;handle_not_player_position&lt;&#x2F;code&gt;的query我们知道在运行该系统时，会处理&lt;strong&gt;不具有&lt;&#x2F;strong&gt;Player组件的实体的位置数据。&lt;&#x2F;p&gt;
&lt;p&gt;对于拆分的table，我们可以很容易的完成并行计算，因为系统1只会查询table1的记录，而系统2则肯定只会查询table2的记录，而不会查询到table1中：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-yuan-xing-de-chuang-jian&quot;&gt;关于原型的创建&lt;&#x2F;h2&gt;
&lt;p&gt;当我们调用Bevy提供的API来构建实体的时候，Bevy就会根据此时所传入的组件列表来&lt;strong&gt;查找并使用&lt;&#x2F;strong&gt;或&lt;strong&gt;新建&lt;&#x2F;strong&gt;的一个原型。即，如果找到了满足当前组件列表定义的原型时，就直接使用该已存在的原型，此时只需要把这一组组件的一些上下文（特别是id关系等）记录到原型中；如果没有找到满足的原型，则新建一个原型实例，同样把上述的相关上下文保存到该新建的原型中，并在后续使用。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bevy通过惰性初始化边的映射关系，仅在首次遇到组件变更时创建新原型并记录边，后续直接复用。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-yuan-xing-de-geng-xin&quot;&gt;关于原型的更新&lt;&#x2F;h2&gt;
&lt;p&gt;注意，这里说的是原型的更新，而非原型下面的某个组件数据的更新，所谓原型的更新发生在其对应实体内组件的增删。比如，对于某个实体具有一个名为&lt;code&gt;Visibilty&lt;&#x2F;code&gt;的组件来标记一个实体是否能被看到，我们想要隐藏该实体时，只需要将&lt;code&gt;Visibility&lt;&#x2F;code&gt;组件从这个实体中移除即可：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;注意，此时该实体id并不会发生变化，只是该实体所包含的组件少了&lt;code&gt;Visibility&lt;&#x2F;code&gt;组件，进而导致该实体所对应的原型发生了变化：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上图中，实体原本属于原型&lt;code&gt;(Enemy, Position, Health, Visibility)&lt;&#x2F;code&gt;，移除&lt;code&gt;Visibility&lt;&#x2F;code&gt;组件后，会被迁移到原型&lt;code&gt;(Enemy, Position, Health)&lt;&#x2F;code&gt;，这个过程会引发上述原型创建的流程，选择已有的原型或建立新的原型。&lt;&#x2F;p&gt;
&lt;p&gt;当然，读者可能会有这样的疑惑，当我们频繁的添加或移除某个实体内的组件时，原型会被频繁的创建，实体信息会被频繁地移动到各个原型中，这其中的性能如何保证呢。对于这个问题，Bevy采取了操作“图”化的设计，即每一个原型实例在其内部会存储一组信息，该信息包含用来记录这个原型一旦发生相关的改变，能够通向的下一个原型的id。&lt;&#x2F;p&gt;
&lt;p&gt;以上面移除&lt;code&gt;Visibility&lt;&#x2F;code&gt;组件为例，改变前的原型&lt;code&gt;(Enemy, Position, Health, Visibility)&lt;&#x2F;code&gt;我们假设称其为&lt;code&gt;AT1&lt;&#x2F;code&gt;，当我们&lt;strong&gt;首次&lt;&#x2F;strong&gt;将某个实体的&lt;code&gt;Visibility&lt;&#x2F;code&gt;组件移除时，由于此刻运行时内部没有其他的原型，所以Bevy会创建一个新的原型&lt;code&gt;(Enemy, Position, Health)&lt;&#x2F;code&gt;（我们假设称其为&lt;code&gt;AT2&lt;&#x2F;code&gt;）；之后，Bevy会生成这样一条上下文信息：“移除&lt;code&gt;Visibility&lt;&#x2F;code&gt;时，指向&lt;code&gt;AT2&lt;&#x2F;code&gt;”，将其存储到&lt;code&gt;AT1&lt;&#x2F;code&gt;中，这条上下文信息在Bevy内部实现被定义为一条&lt;strong&gt;边（Edge）&lt;&#x2F;strong&gt;。如此一来，在后续再次出现同样原型的“敌人角色”实体移除&lt;code&gt;Visibility&lt;&#x2F;code&gt;的时候，可以通过&lt;code&gt;AT1&lt;&#x2F;code&gt;中的存储的“移除&lt;code&gt;Visibility&lt;&#x2F;code&gt;时，指向&lt;code&gt;AT2&lt;&#x2F;code&gt;”来快速索引到&lt;code&gt;AT2&lt;&#x2F;code&gt;中。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-04-26&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;类似状态机的设计&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本文是近期阅读Bevy相关的资料和源码时候的一些所想所感，思来想去最终整理并写下了这篇文章，当然这其中可能有些错误或者不准确的地方，还望读者见谅。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>理解Rust引用及其生命周期标识（下）</title>
        <published>2025-03-30T00:00:00+00:00</published>
        <updated>2025-03-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/理解Rust引用及其生命周期标识（下）/"/>
        <id>https://zhen.wang/article/理解Rust引用及其生命周期标识（下）/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/理解Rust引用及其生命周期标识（下）/">&lt;p&gt;在上一篇文章中，我们围绕 “引用必然存在来源” 这一基本概念，介绍了Rust中引用之间的关系，以及生命周期标记的实际意义。我们首先从最简单的单参数方法入手，通过示例说明了返回引用与输入引用参数之间的逻辑关系；通过多引用参数的复杂场景，阐释了生命周期标注（本人给其命名为 “引用关系标记”）的必要性及其编译器检查机制。在上一篇文章的最后，我们还提到了关于包含引用的结构体，只不过由于篇幅原因以及文章结构原因，我们没有细讲。因此，在本文中，我们将继续通过实际示例出发，探讨包含引用的结构体的生命周期相关内容。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;bao-han-yin-yong-de-jie-gou-ti-de-ben-zhi&quot;&gt;包含引用的结构体的本质&lt;&#x2F;h1&gt;
&lt;p&gt;单从数据结构的角度来看，结构体本质上是具有类型安全的复合数据体，即结构体是一个可以包含多个数据字段的逻辑单元：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is_ok&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;引用的本质也是一份包含了被引用者内存地址信息（以及其他上下文）的数据，因此，我们当然可以让结构体包含引用字段：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;is_ok&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在这里我们先暂且不考虑具体的语法（添加生命周期参数标记），而是思考一下一个包含引用的结构体相比于没有包含任何引用的结构体究竟有什么特殊之处。首先，一个结构体一旦被创建出来，就意味着它内部的数据字段此时都是合法的数据，并且，结构体中的字段数据一定不可能晚于这个结构体创建时刻。&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;Data {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; main
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num_val = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data = Data { num: num_val } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- Data实例化时，里面的字段的数据肯定早于实例化当前Data
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;有的读者可能会给出这样的反例：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;Data {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; main
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; data = Data { num: None };
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num_val = Some(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;data.num = num_val;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;请注意，这里结构体中的&lt;code&gt;num&lt;&#x2F;code&gt;字段类型是&lt;code&gt;Option&amp;lt;i32&amp;gt;&lt;&#x2F;code&gt;，而不是&lt;code&gt;i32&lt;&#x2F;code&gt;，因此，我们需要在创建Data结构体实例数据的时候，把&lt;code&gt;Option&amp;lt;i32&amp;gt;&lt;&#x2F;code&gt;类型数据准备好，这里我们用的是&lt;code&gt;None&lt;&#x2F;code&gt;。这里并没有违背我们上面说的“结构体中的字段数据一定不可能晚于这个结构体创建时刻”。&lt;&#x2F;p&gt;
&lt;p&gt;在笔者看来，一个包含了引用的结构体有如下两个信息点：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;本身可以作为一种引用类型来看。&lt;&#x2F;li&gt;
&lt;li&gt;可以将其创建的实例等价为一个引用。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;我们先看第1点。我们知道，&lt;code&gt;i32&lt;&#x2F;code&gt;是一种类型，&lt;code&gt;&amp;amp;i32&lt;&#x2F;code&gt;也是一种类型。同样的，像上述的&lt;code&gt;MyData&lt;&#x2F;code&gt;这个结构体同样是一种类型。同时，因为该结构体包含了引用，所以我们可以将其&lt;strong&gt;等价理解&lt;&#x2F;strong&gt;为某种引用类型：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上图中，笔者将&lt;code&gt;MyData1&lt;&#x2F;code&gt;归为了普通类型，而将&lt;code&gt;MyData2&lt;&#x2F;code&gt;归为了引用类型。它俩区别在于，&lt;code&gt;MyData1&lt;&#x2F;code&gt;不包含任何的引用字段，而&lt;code&gt;MyData2&lt;&#x2F;code&gt;包含引用字段。&lt;&#x2F;p&gt;
&lt;p&gt;对于第2点，当我们创建一个包含引用的结构体的实例以后，这个实例本身也可以理解为一个引用：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;    ┍ data这个变量本质上一个引用
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data: MyData2 = { num_ref: &amp;amp;num }
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里的&lt;code&gt;data&lt;&#x2F;code&gt;变量，可以等价为一个引用，它类似于这样的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; other = &amp;amp;num;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;只不过在结构体形式下，我们把这个所谓的&lt;code&gt;&amp;amp;num&lt;&#x2F;code&gt;赋值给了结构体内部某个字段而已。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dan-ge-yin-yong-de-jie-gou-ti&quot;&gt;单个引用的结构体&lt;&#x2F;h2&gt;
&lt;p&gt;在大体上能够理解包含引用的结构体的本质以后，我们就可以按照之前的思路，来理解这种含引用的结构体实例变量的其生命周期相关内容了。&lt;&#x2F;p&gt;
&lt;p&gt;首先，一个创建出来的含引用的结构体的实例本身就成为了一个引用数据，而不是普通数据了，那这个引用必然有其来源，而这个引用的来源自然是先前另一个变量借用而来的引用：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;注意看上图，我将&lt;code&gt;num_ref&lt;&#x2F;code&gt;和&lt;code&gt;data&lt;&#x2F;code&gt;圈在了一起，并用“等价”相连接，是因为&lt;code&gt;num_ref&lt;&#x2F;code&gt;一旦设置到了&lt;code&gt;MyData&lt;&#x2F;code&gt;结构体的字段中，就意味着&lt;code&gt;num_ref&lt;&#x2F;code&gt;这个引用被转移到了&lt;code&gt;MyData&lt;&#x2F;code&gt;内部，成为了其一部分，此时&lt;code&gt;data: MyData&lt;&#x2F;code&gt;尽管看起来就是一个普通的数据，但此时它就是一个引用数据。&lt;&#x2F;p&gt;
&lt;p&gt;从上面的关系图我们很容易知道，如果要满足正确的生命周期，很显然，&lt;code&gt;data&lt;&#x2F;code&gt;（&lt;code&gt;num_ref&lt;&#x2F;code&gt;的 “代名词”）不能存活的比其来源&lt;code&gt;num&lt;&#x2F;code&gt;久。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;始终牢记：”引用必然有其来源，且不能活的比其来源更久“&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;duo-ge-yin-yong-de-jie-gou-ti&quot;&gt;多个引用的结构体&lt;&#x2F;h2&gt;
&lt;p&gt;事实上，包含多个引用的结构体本质上和包含单个引用的结构体的理解思路一致的，即结构体中多个引用字段都有其来源，唯一需要注意的为了保证包含多引用的结构体实例在运行时合法，很显然这个结构体实例的存活时间不能超过结构体所包含的多个引用字段的各自存活时间。还是用来源关系图来表达如下的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; val: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;bool &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;true&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data: MyData = {
&lt;&#x2F;span&gt;&lt;span&gt;  num_ref: &amp;amp;num,
&lt;&#x2F;span&gt;&lt;span&gt;  val_ref: &amp;amp;val,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;data&lt;&#x2F;code&gt;来包含了&lt;code&gt;num_ref&lt;&#x2F;code&gt;和&lt;code&gt;val_ref&lt;&#x2F;code&gt;，也就是说，&lt;code&gt;data&lt;&#x2F;code&gt;此时应该视为&lt;code&gt;num_ref&lt;&#x2F;code&gt;和&lt;code&gt;val_ref&lt;&#x2F;code&gt;这两个引用的“结合体”。而&lt;code&gt;num_ref&lt;&#x2F;code&gt;和&lt;code&gt;val_ref&lt;&#x2F;code&gt;又各自来源于&lt;code&gt;num&lt;&#x2F;code&gt;和&lt;code&gt;val&lt;&#x2F;code&gt;，那么为了满足内存安全的要求，我们只有让&lt;code&gt;data&lt;&#x2F;code&gt;的存活时间&lt;strong&gt;同时&lt;&#x2F;strong&gt;不能超过&lt;code&gt;num_ref&lt;&#x2F;code&gt;和&lt;code&gt;val_ref&lt;&#x2F;code&gt;各自所引用的源头数据&lt;code&gt;num&lt;&#x2F;code&gt;和&lt;code&gt;val&lt;&#x2F;code&gt;的存活时间。如果&lt;strong&gt;随时都要同时满足&lt;&#x2F;strong&gt;，就只有让&lt;code&gt;data&lt;&#x2F;code&gt;的存活时间不能超过&lt;code&gt;num&lt;&#x2F;code&gt;和&lt;code&gt;val&lt;&#x2F;code&gt;其中距离销毁时刻最近的那一个：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;jie-gou-ti-de-sheng-ming-zhou-qi-can-shu-biao-shi&quot;&gt;结构体的生命周期参数标识&lt;&#x2F;h1&gt;
&lt;p&gt;目前为止，我们基本理解了包含引用的结构体究竟是一个什么“东西”以及它的存活要求，但Rust中让很多新手难以理解的，其实是结构体中的生命周期参数标识，比如：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;甚至有一些“丧心病狂”的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyDataWrapper&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;my_data&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; wtf!
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;但请不要担心，在阅读了本文以后，我相信你能够很轻松的理解上面这些代码的意义。在继续之前，让我们回顾一下在《理解Rust引用及其生命周期标识（上）》一个例子：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    num_ref
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num_ref = &amp;amp;num;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; res = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(num_ref);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在这个例子中，生命周期参数标识的核心作用，是把&lt;code&gt;func&lt;&#x2F;code&gt;方法的输入引用参数&lt;code&gt;num_ref&lt;&#x2F;code&gt;和输出引用&lt;code&gt;&amp;amp;i32&lt;&#x2F;code&gt;建立&lt;strong&gt;依赖关联&lt;&#x2F;strong&gt;（它们都使用了相同的生命周期参数&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;）。而正是由于该关联关系，我们可以分析出上述的&lt;code&gt;res&lt;&#x2F;code&gt;（返回的引用）本质上依赖&lt;code&gt;num&lt;&#x2F;code&gt;变量。因此，为了内存安全性，我们很显然不能让&lt;code&gt;res&lt;&#x2F;code&gt;这一引用的存活时间超过它的来源&lt;code&gt;num&lt;&#x2F;code&gt;。所以，一旦编译器发现&lt;code&gt;num&lt;&#x2F;code&gt;和&lt;code&gt;res&lt;&#x2F;code&gt;的生命周期不正确时，会予以编译错误。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tian-jia-can-shu-biao-shi-de-bi-yao-xing&quot;&gt;添加参数标识的必要性&lt;&#x2F;h2&gt;
&lt;p&gt;那&lt;strong&gt;为什么包含引用的结构体需要为其添加生命周期参数呢&lt;&#x2F;strong&gt;？在笔者看来，核心作用是为了让开发者通过引用关系标记来更加明确的指定相关的引用依赖关系。让我们用一个例子来更好的解释。&lt;&#x2F;p&gt;
&lt;p&gt;首先，让我们还是定义一个包含引用的结构体：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData { &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 先假设此时没有生命周期参数
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;然后，我们定义如下签名的方法，该方法能够返回一个包含引用的结构体实例：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; MyData;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;基于这个方法签名，无论其内部的代码怎样编写，我们都可以将其简化为如下的流程：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; MyData {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num_ref: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= ???;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data = MyData { num_ref: num_ref };
&lt;&#x2F;span&gt;&lt;span&gt;    data
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;MyData&lt;&#x2F;code&gt;中的&lt;code&gt;num_ref&lt;&#x2F;code&gt;字段是一个引用，基于 “引用不可能凭空产生” ，一定要有一个来源，这里只能是&lt;code&gt;num_ref1&lt;&#x2F;code&gt;或者&lt;code&gt;num_ref2&lt;&#x2F;code&gt;。然而，究竟是&lt;code&gt;num_ref1&lt;&#x2F;code&gt;还是&lt;code&gt;num_ref2&lt;&#x2F;code&gt;呢？很显然我们（以及Rust编译器）是无法通过静态的代码就能分析出，毕竟这是一个运行时才能知道的结果，例如下面的伪代码就没法静态确定：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; MyData {
&lt;&#x2F;span&gt;&lt;span&gt;  	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; current_sec = ... &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 当前运行时的秒数
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num_ref: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;  	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if&lt;&#x2F;span&gt;&lt;span&gt; current_sec % &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2 &lt;&#x2F;span&gt;&lt;span&gt;== &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 秒数为奇数
&lt;&#x2F;span&gt;&lt;span&gt;  		num_ref = num_ref1;
&lt;&#x2F;span&gt;&lt;span&gt;  	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else
&lt;&#x2F;span&gt;&lt;span&gt;  		num_ref = num_ref2;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data = MyData { num_ref: num_ref };
&lt;&#x2F;span&gt;&lt;span&gt;    data
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;既然无法确定返回结构体中的引用字段究竟与哪个入参存在依赖关系，编译器可以做到的一种检查方式就是确保返回的&lt;code&gt;MyData&lt;&#x2F;code&gt;的实例的存活时间不能超过入参&lt;code&gt;num_ref1&lt;&#x2F;code&gt;和&lt;code&gt;num_ref2&lt;&#x2F;code&gt;这两个引用的来源变量存活时间最短的那一个，因为&lt;code&gt;MyData&lt;&#x2F;code&gt;持有的&lt;code&gt;num_ref&lt;&#x2F;code&gt;引用不管依赖哪一个，但只要其存活时间不超过&lt;code&gt;num_ref1&lt;&#x2F;code&gt;和&lt;code&gt;num_ref2&lt;&#x2F;code&gt;所对应的来源变量最先销毁的那个，&lt;code&gt;MyData&lt;&#x2F;code&gt;持有的&lt;code&gt;num_ref&lt;&#x2F;code&gt;就一定是合法的。&lt;&#x2F;p&gt;
&lt;p&gt;尽管这样的处理限制理论上来讲是“最保险最安全”的，但在某些场景下又过于严格了，比如如下的代码从内存安全的角度来看，也是合理的：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; MyData {
&lt;&#x2F;span&gt;&lt;span&gt;  	println!(&amp;#39;{}&amp;#39;, num_ref2) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- num_ref2只用做其它用途，不会与最终返回的MyData产生关系
&lt;&#x2F;span&gt;&lt;span&gt;  	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 返回的MyData只依赖num_ref1，即只依赖num_ref1的来源
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data = MyData { num_ref: num_ref1 }; 
&lt;&#x2F;span&gt;&lt;span&gt;    data
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上述&lt;code&gt;func&lt;&#x2F;code&gt;返回的&lt;code&gt;MyData&lt;&#x2F;code&gt;实例所包含的引用只会来自于&lt;code&gt;num_ref1&lt;&#x2F;code&gt;，永远不会来自&lt;code&gt;num_ref2&lt;&#x2F;code&gt;，也就是说，返回的&lt;code&gt;MyData&lt;&#x2F;code&gt;只需要保证其存活时间不超过&lt;code&gt;num_ref1&lt;&#x2F;code&gt;的来源变量的存活时间即可。但如果按照上述“最安全最保险”的方式进行生命周期检查，Rust编译器是不会给我们通过的。为了即可以保证内存安全，又不过于严格限制引用关系（例如此时这种情况），Rust做法是要求开发者&lt;strong&gt;通过显式的生命周期参数标识&lt;&#x2F;strong&gt;来告诉告知编译器：返回的&lt;code&gt;MyData&lt;&#x2F;code&gt;中的&lt;code&gt;num_ref&lt;&#x2F;code&gt;字段只会和入参&lt;code&gt;num_ref1&lt;&#x2F;code&gt;产生关系。&lt;&#x2F;p&gt;
&lt;p&gt;对于&lt;code&gt;func&lt;&#x2F;code&gt;的入参，只需要给&lt;code&gt;num_ref1&lt;&#x2F;code&gt;和&lt;code&gt;num_ref2&lt;&#x2F;code&gt;分别给予不同的生命周期参数来区分它们：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;但是对于&lt;code&gt;MyData&lt;&#x2F;code&gt;来说，我们应该如何的将入参&lt;code&gt;num_ref1&lt;&#x2F;code&gt;的生命周期参数&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;与&lt;code&gt;MyData&lt;&#x2F;code&gt;中的&lt;code&gt;num_ref&lt;&#x2F;code&gt;这个引用字段进行关联呢？Rust语言规范给出的答案就是对于包含引用的结构体在定义时必须要增加生命周期“形式”参数。比如&lt;code&gt;MyData&lt;&#x2F;code&gt;我们可以这样定义：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;hello&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;hello i32&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;面对上述定义的结构体，我们可以按照这样的理解思路来看：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;MyData&lt;&#x2F;code&gt;放置参数列表的尖括号&lt;code&gt;&amp;lt;xxx&amp;gt;&lt;&#x2F;code&gt;中的&lt;strong&gt;第一个位置是一个引用生命周期参数标识&lt;&#x2F;strong&gt;，这里写作&lt;code&gt;&#x27;hello&lt;&#x2F;code&gt;；&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;MyData&lt;&#x2F;code&gt;中的&lt;code&gt;num_ref&lt;&#x2F;code&gt;这个引用类型的字段的生命周期参数标识使用了参数列表中第一个位置上的的&lt;code&gt;&#x27;hello&lt;&#x2F;code&gt;，因此，在将来我们使用&lt;code&gt;MyData&lt;&#x2F;code&gt;的时候，填入的实际周期参数就对应了&lt;code&gt;num_ref&lt;&#x2F;code&gt;字段。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;紧接着，我们不气上面的方法签名。此时，我们只需要在返回的&lt;code&gt;MyData&lt;&#x2F;code&gt;把实际的生命周期参数标识&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;填入到尖括号中即可：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;而此时的&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;这个生命周期参数标识叫做“实际参数 ”，它放在了参数列表的第一位，指代了&lt;code&gt;MyData&lt;&#x2F;code&gt;在定义时的参数&lt;code&gt;&#x27;hello&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;至此，我们就完成了整个依赖的链路的确定。相信读者在阅读了上述的内容以后，能够理解对于包含引用的结构体添加需要添加生命周期参数标识的必要性了吧。记住，对于结构体上定义时的生命周期参数标识，是一种标记，它在参数列表（就是结构体名称后面的尖括号列表&lt;code&gt;&amp;lt;xxx, xxx&amp;gt;&lt;&#x2F;code&gt;）中的位置用于在将来实际使用时传入到对应的位置来表达实际的意义。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;zhu-yi-jie-gou-ti-yu-jie-gou-ti-yin-yong&quot;&gt;注意结构体与结构体引用&lt;&#x2F;h1&gt;
&lt;p&gt;关于包含结构体引用的实例还有一个需要读者注意点就是仔细区分结构体实例与其借用而来的引用。例如下面的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;data_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a &lt;&#x2F;span&gt;&lt;span&gt;MyData&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上述的方法有两个生命周期参数标识&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;和&lt;code&gt;&#x27;b&lt;&#x2F;code&gt;，其中&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;用于标记&lt;code&gt;&amp;amp;MyData&lt;&#x2F;code&gt;这个结构体实例的引用；而&lt;code&gt;&#x27;b&lt;&#x2F;code&gt;则用于标记&lt;code&gt;MyData&lt;&#x2F;code&gt;实例中的字段&lt;code&gt;num_ref&lt;&#x2F;code&gt;这个引用。注意它俩有着不同的概念，用依赖图可能更加直接：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-03-30&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;data_ref&lt;&#x2F;code&gt;依赖&lt;code&gt;data&lt;&#x2F;code&gt;，而&lt;code&gt;data&lt;&#x2F;code&gt;包含&lt;code&gt;num_ref&lt;&#x2F;code&gt;，即依赖于&lt;code&gt;num&lt;&#x2F;code&gt;，因此&lt;code&gt;data_ref&lt;&#x2F;code&gt;的生命周期存活时间，不能超过&lt;code&gt;num&lt;&#x2F;code&gt;的存活时间。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;sheng-ming-zhou-qi-can-shu-biao-ji-bu-gai-bian-ke-guan-cun-zai-de-sheng-ming-zhou-qi&quot;&gt;生命周期参数标记不改变客观存在的生命周期&lt;&#x2F;h1&gt;
&lt;p&gt;很多Rust新手可能会有这样的误区，认为当修改了或者设置了方法的生命周期参数标记的时候，就会改变实际传入的变量的生命周期，这是很多新手无法掌握生命周期参数标记的典型问题。但实际上，生命周期参数标记的核心作用是通过语法约束向编译器提供引用关系的逻辑描述，而不会改变引用本身客观存在的生命周期范围。通常，我们需要从“客观生命周期事实”和“主观引用关系逻辑描述”两个方面来看待包含生命周期参数标记的代码。例如，如下的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ... ... 
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; result: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;  {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;  	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; num_ref: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &amp;amp;num;
&lt;&#x2F;span&gt;&lt;span&gt;  	result = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(num_ref);
&lt;&#x2F;span&gt;&lt;span&gt;  }
&lt;&#x2F;span&gt;&lt;span&gt;  println!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, result);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;从“客观生命周期事实”的角度来看，&lt;code&gt;result&lt;&#x2F;code&gt;这个&lt;code&gt;&amp;amp;i32&lt;&#x2F;code&gt;引用的生命周期是最长的，比起&lt;code&gt;num_ref&lt;&#x2F;code&gt;以及&lt;code&gt;num&lt;&#x2F;code&gt;都长；而“主观引用关系逻辑描述”来看，这个&lt;code&gt;result&lt;&#x2F;code&gt;是由&lt;code&gt;func&lt;&#x2F;code&gt;输出而来，而观察该方法的签名，我们知道通过&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;引用生命周期参数标记，返回的引用生命周期依赖于入参，而入参是&lt;code&gt;num_ref&lt;&#x2F;code&gt;，来源于&lt;code&gt;num&lt;&#x2F;code&gt;，因此它不能超过&lt;code&gt;num&lt;&#x2F;code&gt;的生命周期。因此，我们（Rust编译器）能够根据其中的矛盾点而识别到错误。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本文在编写过程中也是断断续续，修修改改了有小半个月才完成，虽然文章已经编写了完成了，但是笔者还有很多内容想说，就放在后续的文章讲吧。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>理解Rust引用及其生命周期标识（上）</title>
        <published>2025-02-28T00:00:00+00:00</published>
        <updated>2025-02-28T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/理解Rust引用及其生命周期标识（上）/"/>
        <id>https://zhen.wang/article/理解Rust引用及其生命周期标识（上）/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/理解Rust引用及其生命周期标识（上）/">&lt;h1 id=&quot;xie-zai-qian-mian&quot;&gt;写在前面&lt;&#x2F;h1&gt;
&lt;p&gt;作为Rust开发者，你是否还没有完全理解引用及其生命周期？是否处于教程一看就会，但在实际开发过程中不知所措？本文将由浅入深，手把手教你彻底理解Rust引用与生命周期。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;关于本文的理解门槛&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;本文主要面向的是已经基本上了解过Rust这门语言，对引用以及生命周期（及其标识）有基本的了解，但对于包含生命周期标识的复杂场景理解吃力的Rust开发者。因此本文不会赘述讨论关于引用的语法形式，像是如果连下面的例子为什么会报错都不清楚原因的话，那么本篇就不太适合阅读了。&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; r;
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; x = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        r = &amp;amp;x;
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;    println!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;r: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, r);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;bao-han-yin-yong-de-fang-fa&quot;&gt;包含引用的方法&lt;&#x2F;h1&gt;
&lt;p&gt;让我们从一个最简单的例子开始，假设有如下的方法签名：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;大多数的教程都会告诉你：“入参是一个引用，返回也是一个引用，在这里，返回的引用的生命周期不能超过入参引用的生命周期，... ...”。这样说确实没有错，但这句话本质上是一个结论，对于不熟悉Rust生命周期的人来说，无法清晰地理解其中的逻辑原理，是不会真正掌握这块的内容。&lt;&#x2F;p&gt;
&lt;p&gt;接下来让我们进入正题。回到上面的例子，观察这个方法签名，我们已经知道了这个方法有一个引用作为入参，且返回的也是一个引用。那它们俩有没有关联呢？答案就是：在这种场景中，即使没有生命周期标识，它俩也一定存在关系。&lt;&#x2F;p&gt;
&lt;p&gt;在讲原因前，让我们先理解一个基本的事实：&lt;strong&gt;引用不可能凭空产生，它一定是来源于某个实际变量&lt;&#x2F;strong&gt;。有了这个基本的事实，让我们再来分析这个方法签名。&lt;&#x2F;p&gt;
&lt;p&gt;首先入参是一个引用。考虑到“引用一定存在来源”，那么这个入参引用会来自于什么呢？很容易想到，就是调用该方法时，外部某个变量借用而来的到的引用，作为了此时的入参：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 一些代码...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; data: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 调用方法
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;data); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 方法的入参这个引用，来源于调用方法前某个变量借用而来得到的引用
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;入参引用我们分析好了，接下里让我们来分析返回值。返回值是一个引用，我们依然套用上面的“引用一定有其来源”来思考，这里返回的引用的来源是什么呢？首先我们考虑这里返回的引用会不会是方法中的局部变量借用而来，很显然不可能。假设代码如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; some_data: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; some_ref: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &amp;amp;some_data;
&lt;&#x2F;span&gt;&lt;span&gt;    some_ref &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在这里，我们在方法体内部创建了一个i32类型的变量，得到它的引用，再通过方法返回。然而，&lt;code&gt;some_data&lt;&#x2F;code&gt;是方法的局部变量，一旦&lt;code&gt;func&lt;&#x2F;code&gt;方法执行完毕，&lt;code&gt;some_data&lt;&#x2F;code&gt;变量对应的内存就会被释放，那么返回给外部的&lt;code&gt;some_ref&lt;&#x2F;code&gt;就成了无效的悬垂引用（Dangling References），引用着一段无效的内存。对于这种情况，Rust编译器可以非常容易的推断出你的代码语义，并禁止这种情况出现，No Way！因此，该方法返回的引用的来源就不可能是一个方法中的局部变量。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;稍有经验的读者可能会想到使用&lt;code&gt;&#x27;static&lt;&#x2F;code&gt;这个特殊的生命周期标识来绕过我们的例子，但请不要着急，在本文的后面我们会提到的。当然，如果你还不太明白&lt;code&gt;&#x27;static&lt;&#x2F;code&gt;，那么太好了，可以完全忽略这段话。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;既然不可能是来源于借用一个局部变量得到的结果，那么对于这个例子来说，我们就只能让其和入参进行关联了，例如编写如下的代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    num &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 咱们直接把入参引用返回出去
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;虽然这个例子很简单，但是我们可以从中联想：虽然我们无法知道这个例子中&lt;code&gt;func&lt;&#x2F;code&gt;方法的具体实现，但这里方法的入参引用和出参引用之间，会因为“引用一定要有来源”这一事实，而形成一种关系：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-02-28&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;也就是说，&lt;code&gt;num_ref&lt;&#x2F;code&gt; 来源于 &lt;code&gt;num&lt;&#x2F;code&gt;，而&lt;code&gt;return_ref&lt;&#x2F;code&gt; 又来源于 &lt;code&gt;num_ref&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-02-28&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;既然存在来源关系，那么按照朴素的思维逻辑，被产出者不能存活的比被来源者还久（“我”来源于“你”，而“你”先没了，那“我”咋办 &amp;gt;_&amp;lt; ）。&lt;&#x2F;p&gt;
&lt;p&gt;因此，那我们可以非常自然地给出结论：&lt;code&gt;num_ref&lt;&#x2F;code&gt;不能存活的比&lt;code&gt;num&lt;&#x2F;code&gt;久，而&lt;code&gt;return_ref&lt;&#x2F;code&gt;不能存活的比&lt;code&gt;num_ref&lt;&#x2F;code&gt;久。也就是说，对于这个方法：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;)  -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;也就是说，入参引用的要比返回的引用存活的更长才行。&lt;&#x2F;p&gt;
&lt;p&gt;值得注意的是，在本例中，目前为止，我们完全没有把Rust生命周期那套东西搬出来，仅仅是通过简单的关系逻辑梳理，就能分析出上述”生命周期“的关系。&lt;&#x2F;p&gt;
&lt;p&gt;此外，在这个场景中，我们即使不给方法上添加生命周期标识，也能通过Rust编译器的检查，毕竟，这里单个入参引用和出参引用一定有来源关系。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-sheng-ming-zhou-qi-biao-ji&quot;&gt;关于生命周期标记&lt;&#x2F;h2&gt;
&lt;p&gt;其实一直以来，笔者都认为“生命周期标记” 这个命名存在一定的误导性。在笔者看来，这个东西更加适合叫做 “&lt;strong&gt;引用关系标记&lt;&#x2F;strong&gt;”，所以在本文，接下来内容中，笔者都将使用 “引用关系标记” 来书写表述。还是上面的例子，当我们手动加上引用关系标记以后如下所示：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上述方法签名表达了这样一种意思：入参引用与返回的引用存在关联关系，因为它俩都用了同一个 &lt;strong&gt;引用关系标记（‘a）&lt;&#x2F;strong&gt; 来标识。当然，我们前面已经分析知道了，在单个引用入参，然后返回引用的场景下，入参引用与出参引用会存在关系。因此，这里的引用关系标记可以移除。&lt;&#x2F;p&gt;
&lt;p&gt;那我们可以是不是可以完全不用引用关系标记呢？让我们用一个非常经典的例子来进一步分析说明：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上面的方法签名有2个输入引用和1个输出引用。同样基于 “引用一定存在来源” 的思路来分析引用，入参的&lt;code&gt;num1&lt;&#x2F;code&gt;、&lt;code&gt;num2&lt;&#x2F;code&gt;和之前一样就是来自调用该方法时，外部某个变量借用而来的引用。&lt;&#x2F;p&gt;
&lt;p&gt;然而，当我们试图分析返回的引用的来源时，会发现有点困难了。返回的&lt;code&gt;&amp;amp;i32&lt;&#x2F;code&gt;首先不可能是方法内局部变量借用而来，所以依然与入参引用有关，那究竟是与&lt;code&gt;num1&lt;&#x2F;code&gt;有关还是与&lt;code&gt;num2&lt;&#x2F;code&gt;有关呢？回答是：没法确定。比如下面的例子：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    num_ref1
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这种情况，一看就知道，返回的引用只与&lt;code&gt;num1&lt;&#x2F;code&gt;这个输入引用有关系。然而，如果这个方法的实现改为了：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;&#x2F; 伪代码
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    如果运行时，此刻的秒钟为偶数
&lt;&#x2F;span&gt;&lt;span&gt;        返回 num_ref1
&lt;&#x2F;span&gt;&lt;span&gt;    否则
&lt;&#x2F;span&gt;&lt;span&gt;        返回 num_ref2
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此刻，返回引用究竟与&lt;code&gt;num1&lt;&#x2F;code&gt;有关还是与&lt;code&gt;num2&lt;&#x2F;code&gt;有关，就需要根据实际运行时情况而动态变化了，我们只能说：可能与输入引用&lt;code&gt;num1&lt;&#x2F;code&gt;有关，可能与输入引用&lt;code&gt;num2&lt;&#x2F;code&gt;有关。这时候关系图就如下所示：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-02-28&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;前面也提到，A来源于B，那么A的存活不能超过B，否则，B都没了，A就没有存在的价值了。现在根据上述的关系图，在某些时候，&lt;code&gt;return_ref&lt;&#x2F;code&gt;会来源于&lt;code&gt;num_ref1&lt;&#x2F;code&gt;，因此&lt;code&gt;return_ref&lt;&#x2F;code&gt;的存活不能超过&lt;code&gt;num_ref1&lt;&#x2F;code&gt;；在另外的某些时候，&lt;code&gt;return_ref&lt;&#x2F;code&gt;会来源于&lt;code&gt;num_ref2&lt;&#x2F;code&gt;，因此&lt;code&gt;return_ref&lt;&#x2F;code&gt;的存活不能超过&lt;code&gt;num_ref2&lt;&#x2F;code&gt;。既然两种情况都会出现，&lt;strong&gt;同时又为了保证无论任何情况下都不会出现悬垂引用&lt;&#x2F;strong&gt;，我们能很自然的会做出这样的限定：&lt;code&gt;return_ref&lt;&#x2F;code&gt;不能超过入参&lt;code&gt;num_ref1&lt;&#x2F;code&gt;和&lt;code&gt;num_ref2&lt;&#x2F;code&gt;中最短的那个引用的存活时间。&lt;&#x2F;p&gt;
&lt;p&gt;然而，当我们按照上述思路，不加任何的引用关系标记，Rust是编译不通过的。因为我们确实在实际的场景中，会存在这样的逻辑：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;func&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num_ref2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    num_ref1 &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- 确实只与num_ref1有关，跟num_ref2没有任何关系
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;你可能会觉得这可以让Rust编译器来进行分析。然而，方法逻辑的实现千千万万，Rust不能case by case的方式来理解你程序的业务逻辑，进而推断出返回的引用究竟与输入的一堆引用的中哪些有关。&lt;&#x2F;p&gt;
&lt;p&gt;因此，Rust干脆说：“嗨，引用关系你标识出来吧，我只关心引用的存活周期是否满足就好了”。也就是说，Rust编译器在处理引用安全性这方面只做好借用检查与引用生命周期的判断，至于一个方法的输入、输出的引用的关系，程序员标记好即可，这样，Rust编译器只需要关心方法签名就行。&lt;&#x2F;p&gt;
&lt;p&gt;至此，让我们再通过几个例子来巩固目前讲的内容。&lt;&#x2F;p&gt;
&lt;p&gt;示例1：输出引用与输入的n个引用都有关&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fun1&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;*num1 &amp;gt; *num2 {
&lt;&#x2F;span&gt;&lt;span&gt;        num1
&lt;&#x2F;span&gt;&lt;span&gt;    } &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;else &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;        num2
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这种场景下，我们一般使用同一引用关系标记（这里就是&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;）把它们都“关联“起来。在编译器的视角来看：”噢，这个方法返回的引用与入参的两个引用都有关系，那么作为编译器的我，要保证返回的引用存活时间不能比入参两个引用中最短的那个都存活的更长，这样才能无论哪种情况，都不会出现悬垂引用。“&lt;&#x2F;p&gt;
&lt;p&gt;示例2：输出引用只与输入的某些有关&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fun2&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;b i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    num1
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在这个例子中，我们引入了两个引用关系标记（&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;、&lt;code&gt;&#x27;b&lt;&#x2F;code&gt;），同时，返回引用标记的是&lt;code&gt;&#x27;a&lt;&#x2F;code&gt;，与输入引用中&lt;code&gt;num1&lt;&#x2F;code&gt;保持一致。那么编译器在编译过程中进行生命周期检查的时候，其视角就是：“返回引用&lt;strong&gt;只与&lt;&#x2F;strong&gt;&lt;code&gt;num1&lt;&#x2F;code&gt;这个引用参数存在关系，那么我接下来进行检查的时候，只需要检查一下，返回的引用存活周期不要超过&lt;code&gt;num1&lt;&#x2F;code&gt;这个引用的存活周期即可，其他就不用管了”。同时，这个例子还可以修改为：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fun2&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num1&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;num2&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&amp;#39;_ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;a i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    num1
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;既然与第二个参数没关系，那第二个参数就写成&lt;code&gt;&#x27;_&lt;&#x2F;code&gt;吧。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;te-shu-qing-kuang&quot;&gt;特殊情况&lt;&#x2F;h2&gt;
&lt;p&gt;基于前面我们提到的“引用一定有来源”，所以一般情况下这种事不会出现的：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;方法没有入参，又能返回一个&lt;code&gt;i32&lt;&#x2F;code&gt;变量的引用，似乎引用的来源只能是方法中局部变量借用而来，但我们知道这是不被允许的。&lt;&#x2F;p&gt;
&lt;p&gt;但我们还可以这样编写：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NUM&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;i32 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;&amp;#39;static i32 &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;NUM
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    println!(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;num: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;{}&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;fun&lt;&#x2F;span&gt;&lt;span&gt;()); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 可以输出：“num: 5”
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;一个常量，因为其生命周期贯穿整个程序，活得最久，因此我们可以在方法中返回一个常量的引用，但需要注意的是，我们使用&lt;code&gt;&#x27;static&lt;&#x2F;code&gt;这个特殊的生命周期标记。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;yu-gao-bao-han-yin-yong-de-jie-gou-ti&quot;&gt;预告：包含引用的结构体&lt;&#x2F;h1&gt;
&lt;p&gt;实际上，除开方法可能会包含输入、输出引用以外。我们还会面临包含引用的结构体。考虑到读者最好对于本文能够好消化，笔者决定将包含引用的结构体的情况放到下一篇文章中来继续探讨。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Wgpu图文详解（05）纹理与绑定组</title>
        <published>2025-01-16T00:00:00+00:00</published>
        <updated>2025-01-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Wgpu图文详解（05）纹理与绑定组/"/>
        <id>https://zhen.wang/article/Wgpu图文详解（05）纹理与绑定组/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Wgpu图文详解（05）纹理与绑定组/">&lt;blockquote&gt;
&lt;p&gt;⚠️本文编写时，wgpu最新主版本已升级至24，与先前版本存在差异，请读者自行阅读官方：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gfx-rs&#x2F;wgpu&#x2F;releases&#x2F;tag&#x2F;v24.0.0&quot;&gt;change log&lt;&#x2F;a&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;shen-me-shi-wen-li&quot;&gt;什么是纹理？&lt;&#x2F;h2&gt;
&lt;p&gt;纹理是图形渲染中用于增强几何图形视觉效果的一种资源。它是一个二维或三维的数据数组，通常包含颜色信息，但也可以包含其他类型的数据，如法线、高度、环境光遮蔽等。纹理的主要目的是为几何图形的表面提供详细的视觉效果，使其看起来更加真实和复杂。而我们常见的图片是一个二维的像素数组，每个像素包含颜色信息（通常是 RGB 或 RGBA）。图片可以是任何内容，如照片、图标、绘画等。图片的主要用途是显示和存储视觉信息，可以用于各种应用，如网页、文档、多媒体等。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;wen-li-he-tu-pian-de-chai-yi&quot;&gt;纹理和图片的差异&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;用途&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纹理&lt;&#x2F;strong&gt;：主要用于图形渲染，增强几何图形的视觉效果。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;图片&lt;&#x2F;strong&gt;：主要用于显示和存储视觉内容，应用范围更广泛。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;数据结构&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纹理&lt;&#x2F;strong&gt;：可以是二维或三维的数据数组，包含多种类型的数据（如颜色、法线、高度等）。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;图片&lt;&#x2F;strong&gt;：通常是二维的像素数组，主要包含颜色信息。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;处理方式&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纹理&lt;&#x2F;strong&gt;：通过图形渲染管线进行处理，涉及纹理坐标、采样器、片段着色器等。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;图片&lt;&#x2F;strong&gt;：通过图像处理软件或库进行处理，直接操作像素数据。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;存储和传输&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;纹理&lt;&#x2F;strong&gt;：通常存储在 GPU 内存中，优化为渲染操作。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;图片&lt;&#x2F;strong&gt;：可以存储在文件系统中，格式多样（如 JPEG、PNG、BMP 等），用于各种应用。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;wen-li-yu-bang-ding-zu-shi-jian&quot;&gt;纹理与绑定组实践&lt;&#x2F;h1&gt;
&lt;p&gt;在理解纹理的基本概念以后，我们基于第四章的项目内容初始化本章代码目录。要得到纹理数据，我们首先需要加载一张图片到内存中。因此，我们先放置一张样例图片到项目目录下的assets目录中（一般将资源文件放到assets目录中）；同时，引入&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;image&quot;&gt;image&lt;&#x2F;a&gt; 包，该包提供了读写图片数据的能力，以方便我们后续加载图片；最后，我们增加一个&lt;code&gt;img_utils.rs&lt;&#x2F;code&gt;的源代码文件，该文件中封装了一个名为&lt;code&gt;RgbaImg&lt;&#x2F;code&gt;的结构体来抽象图片数据。初始准备完成后，现在的项目结构如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;初始化内容可checkout该 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;commit&#x2F;5adbdd93adde63c9ced444be3537fcfed301f138&quot;&gt;commit&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;接下来，我们会在合适的位置通过RgbaImg实例来构造出纹理对象，并将其按照之前的思路存储到WgpuCtx实例中。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;gou-zao-wen-li-texture&quot;&gt;构造纹理Texture&lt;&#x2F;h2&gt;
&lt;p&gt;首先，我们需要在WgpuCtx结构体中增加三个字段：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;texture: wgpu::Texture&lt;&#x2F;code&gt;：存储纹理Texture实例。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;texture_image: RgbaImg&lt;&#x2F;code&gt;：存储图片的原始数据（宽、高、像素二进制数据）。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;texture_size: wgpu::Extent3d&lt;&#x2F;code&gt;：纹理的尺寸定义。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;texture: wgpu::Texture&lt;&#x2F;code&gt;是一个纹理实例，主要是保存了GPU在渲染纹理过程中的一些上下文信息（譬如纹理的宽高深度等）；&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;texture_image: RgbaImg&lt;&#x2F;code&gt;主要是存储我们待会儿加载示例图片后，得到的图片的信息（宽高二进制数据等）；&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;texture_size: wgpu::Extent3d&lt;&#x2F;code&gt;主要是存储了纹理的宽高登信息。&lt;&#x2F;p&gt;
&lt;p&gt;在WgpuCtx结构体中定义好字段以后，我们需要完成纹理和图片的构造过程。这个过程其实和之前创建顶点缓冲区的思路类似，就是在&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;的&lt;code&gt;new_async&lt;&#x2F;code&gt;方法的合适位置调用相关的API进行构造：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;首先，我们调用&lt;code&gt;Rgba&lt;&#x2F;code&gt;的&lt;code&gt;new&lt;&#x2F;code&gt;方法，传入项目下的示例图片路径，构造出&lt;code&gt;RgbaImg&lt;&#x2F;code&gt;对象，该对象存储了图片的宽高以及rgba格式的二进制数据（&lt;strong&gt;注意，这里的路径需要传递一个绝对路径，请读者自己将图片放置到合适位置，并在这里填写正确的绝对路径，否则运行时会找不到&lt;&#x2F;strong&gt;）。&lt;&#x2F;p&gt;
&lt;p&gt;然后，我们通过图片的宽高信息来直接构造了一个&lt;code&gt;Extend3d&lt;&#x2F;code&gt;对象。&lt;&#x2F;p&gt;
&lt;p&gt;最后，我们调用&lt;code&gt;wgpu::Device&lt;&#x2F;code&gt;的&lt;code&gt;create_texture&lt;&#x2F;code&gt;API来创建一个纹理对象，该方法参数&lt;code&gt;TextureDescriptor&lt;&#x2F;code&gt;的具体配置如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在这里，我们梳理一遍&lt;code&gt;create_texture&lt;&#x2F;code&gt;参数字段（label字段不在赘述）：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;size: wgpu::Extend3d&lt;&#x2F;code&gt;：用于表达纹理的基本尺寸结构（宽、高以及深度）。在这里，我们将前面获取到的图片的宽高作为了Extend3d的宽高；对于Extend3d的&lt;code&gt;depth_or_array_layers&lt;&#x2F;code&gt;字段，理论下，纹理是以3维形式存储的，但是在本例中我们主要是为了渲染一个2D图片纹理，所以在这里我们传入&lt;code&gt;1&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;mip_level_count&lt;&#x2F;code&gt;与&lt;code&gt;sample_count&lt;&#x2F;code&gt;我们暂时先不讲，因为涉及到的内容稍微有点复杂，不是一两句能讲清楚的，等到后面实践过程中再来回顾该字段，这里目前就设置为&lt;code&gt;1&lt;&#x2F;code&gt;即可。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;dimension: wgpu::TextureDimension&lt;&#x2F;code&gt;：表明纹理的维度，我们传入表示2D的枚举。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;format: wgpu::TextureFormat&lt;&#x2F;code&gt;：表明我们使用的纹理的原始数据格式。在前面我们加载图片数据后，将其转换为了u8rgba，即四个字节（u8）每个字节分别对应Red、Green、Blue、Alpha通道的数据，同时，大部分图像数据使用sRGB来存储，所以这里我们选择了对应的枚举：&lt;code&gt;Rgba8UnormSrgb&lt;&#x2F;code&gt;。关于图像格式的细节，不在本文讨论，感兴趣的读者自行拓展学习：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zh.wikipedia.org&#x2F;wiki&#x2F;SRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4&quot;&gt;sRGB色彩空间&lt;&#x2F;a&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;usage: wgpu::TextureUsages&lt;&#x2F;code&gt;：该字段用来表示我们的纹理数据将会被怎样使用，在这里我们设置两个：&lt;code&gt;TEXTURE_BINDING&lt;&#x2F;code&gt;与&lt;code&gt;COPY_DST&lt;&#x2F;code&gt;。前者表示我们要在着色器中使用这个纹理，即在稍后的过程中我们会在着色器中通过一定的方式访问到这个纹理；后者表示我们能将数据复制到这个纹理上，即我们后续会有将数据写入到纹理上的操作。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;view_formats&lt;&#x2F;code&gt;也是一个比较复杂的字段，我们暂时先不关心它，传入&lt;code&gt;&amp;amp;[]&lt;&#x2F;code&gt;即可。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;细心的读者发现了，当我们创建&lt;code&gt;wgpu::Texture&lt;&#x2F;code&gt;的时候，会首先创建该&lt;code&gt;wgpu::Extent3d&lt;&#x2F;code&gt;实例，并通过该&lt;code&gt;Extent3d&lt;&#x2F;code&gt;再来创建&lt;code&gt;Texture&lt;&#x2F;code&gt;，那么理论上，Texture实例是包含了&lt;code&gt;Extend3d&lt;&#x2F;code&gt;的，那么为什么这里需要将再额外&lt;code&gt;Extend3d&lt;&#x2F;code&gt;保存下来呢？原因在于在接下来的流程中，我们还会消费&lt;code&gt;Extend3d&lt;&#x2F;code&gt;，但是Texture没有相关的API来访问内部的&lt;code&gt;Extend3d&lt;&#x2F;code&gt;数据。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;tian-chong-shu-ju-dao-wen-li&quot;&gt;填充数据到纹理&lt;&#x2F;h2&gt;
&lt;p&gt;在完成对纹理实例的创建并保存到&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;后，我们需要调用Queue队列的&lt;code&gt;write_texture&lt;&#x2F;code&gt; API 来将我们的图片数据通过队列的方式写入到纹理中。具体做法则是在我们先前在&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;中定义的&lt;code&gt;draw&lt;&#x2F;code&gt;方法中，在最后调用队列的&lt;code&gt;submit&lt;&#x2F;code&gt;方法前调用&lt;code&gt;write_texture&lt;&#x2F;code&gt;API：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，这一步我们并不是将纹理数据写入到渲染流程中，而是通过队列提供的API，将图片像素数据写入到纹理中，即完成纹理和实际二进制数据的关联。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于&lt;code&gt;write_texture&lt;&#x2F;code&gt;API来说，它的参数现阶段有四个，我们依次讲解：&lt;&#x2F;p&gt;
&lt;p&gt;第一个参数我们需要传递一个&lt;code&gt;TexelCopyTextureInfo&lt;&#x2F;code&gt;对象，该对象引用了我们刚刚创建的纹理对象（&lt;code&gt;self.texture&lt;&#x2F;code&gt;），&lt;code&gt;TexelCopyTextureInfo&lt;&#x2F;code&gt;并不是一个纹理对象，而是一个用来描述纹理操作配置信息的对象，它表达这样一个意图：期望将接下来传入的图像数据以&lt;strong&gt;拷贝&lt;&#x2F;strong&gt;的方式填充到纹理&lt;code&gt;self.texture&lt;&#x2F;code&gt;中。至于其中的&lt;code&gt;mip_level&lt;&#x2F;code&gt;、&lt;code&gt;origin&lt;&#x2F;code&gt;以及&lt;code&gt;aspect&lt;&#x2F;code&gt;字段这里我们先暂时不介绍，读者按照默认即可。&lt;&#x2F;p&gt;
&lt;p&gt;第二个参数传递的是我们保存的图像像素数据（&lt;code&gt;self.texture.bytes&lt;&#x2F;code&gt;）。这里就是我们实际要填充到纹理中的图像数据。&lt;&#x2F;p&gt;
&lt;p&gt;第三个参数我们需要传入一个&lt;code&gt;TexelCopyBufferLayout&lt;&#x2F;code&gt;对象，如果读者对先前的文章比较熟悉，那么会记得在Wgpu中，常规数据需要结合XxxLayout布局才能描述一个原始的二进制数据的形态究竟是什么（比如之前的顶点缓冲区布局）。在这里也是同样一个思路，当我们传入的图片数据在没有任何其他上下文定义的情况下，GPU内部是无法理解这个二进制数据是什么结构，因此才会有&lt;code&gt;TexelCopyBufferLayout&lt;&#x2F;code&gt;。回到本例的具体配置中，&lt;code&gt;TexelCopyBufferLayout&lt;&#x2F;code&gt;的&lt;code&gt;offset&lt;&#x2F;code&gt;字段为&lt;code&gt;0&lt;&#x2F;code&gt;，代表了我们传入的二进制数据从0开始就是图片数据；&lt;code&gt;bytes_per_row&lt;&#x2F;code&gt;字段表示了图片的每一行有多少个字节，由于我们的图片数据每一个像素由rgba四个属性组成，每个属性占用1个字节，即一个像素总共占用4个字节，对于一行来说，我们有 &lt;em&gt;图片宽度n&lt;&#x2F;em&gt; 个像素，因此这里每一行的字节数就是&lt;code&gt;4 x 图片宽度n&lt;&#x2F;code&gt;；至于&lt;code&gt;rows_per_image&lt;&#x2F;code&gt;，就不难理解了，总共有 &lt;em&gt;图片高度m&lt;&#x2F;em&gt; 行。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;最后一个参数就是我们先前存储的&lt;code&gt;Extend3d&lt;&#x2F;code&gt;对象，这里就不再赘述意义了。&lt;&#x2F;p&gt;
&lt;p&gt;因此，调用了该方法后，我们就将真实的图片数据填充到纹理对象中。接下来我们就可以直接使用了吗？答案是否定的，纹理在准备完成以后，我们无法很直接的使用它。接下来我会详细说明这一块的内容。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，该处代码使用的wgpu版本已升级至24.x，其中对于TexelCopyTextureInfo和TexelCopyBufferLayout结构体，先前的版本名称是ImageCopyTexture和ImageDataLayout。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;chuang-jian-cai-yang-qi&quot;&gt;创建采样器&lt;&#x2F;h2&gt;
&lt;p&gt;纹理采样器Sampler是一个渲染管线中的组件，它负责根据顶点坐标等信息，从纹理图像中提取出具体的像素颜色值，用于对渲染的物体表面进行纹理映射。下图是一个不太严谨但易于理解的图示来解释采样器的核心工作：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;左边是一个纹理图片数据，而右边则是最终要渲染的目标平面。采样器就类似于取色器一样的东西，当右边渲染平面上某一处像素位置要渲染具体的颜色的时候，采样器会通过一定的规则去左边的纹理数据中取到对应的颜色。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;请务必注意，采样器基本的工作流程是依据右边的渲染目标，再从左边的纹理数据中找到对应的颜色数据，而不是从左侧的纹理数据开始，往右边渲染目标填充。这二者是有一定的区别的。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;理解采样器的基本工作流程后，让我们来创建一个采样器。采样器尽管听起来像是一个工具，但在Wgpu中也算做一种资源，我们保持和之前的方式一样，首先在&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;结构体中增加一个名为&lt;code&gt;sampler&lt;&#x2F;code&gt;的字段，类型为&lt;code&gt;wgpu::Sampler&lt;&#x2F;code&gt;，用来保存我们创建的采样器；其次，在&lt;code&gt;new_async&lt;&#x2F;code&gt;的方法中合适位置调用&lt;code&gt;device&lt;&#x2F;code&gt;的&lt;code&gt;create_sampler&lt;&#x2F;code&gt;API，我们就可以创建一个采样器：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;我们可以看到关于&lt;code&gt;SamplerDescriptor&lt;&#x2F;code&gt;中的三个&lt;code&gt;address_mode_*&lt;&#x2F;code&gt;字段。对于UVW坐标来说：&lt;&#x2F;p&gt;
&lt;p&gt;U和V：通常对应于二维纹理图像的水平和垂直方向的坐标。U坐标类似于二维图像中的X坐标，表示纹理图像的水平位置；V坐标类似于二维图像中的Y坐标，表示纹理图像的垂直位置。它们的取值范围一般在0到1之间，其中(0,0)通常表示纹理图像的左下角，(1,1)表示纹理图像的右上角。&lt;&#x2F;p&gt;
&lt;p&gt;W：在一些情况下，会使用到W坐标。W坐标主要用于三维纹理映射，即纹理图像本身是三维的，或者在进行一些特殊的纹理映射操作时需要额外的一个维度来控制纹理的采样。例如，在体积渲染中，纹理图像可以是一个三维的数据体，W坐标就表示在这个三维数据体中的深度方向的位置。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;对于&lt;code&gt;address_mode_*&lt;&#x2F;code&gt;字段的枚举值，它的作用是指定了当采样器采样的坐标超出了纹理本身的边界时，应该如何处理。同样的，我们可以用下面不严谨的图示来描述具体的场景：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;比如在上图中，位置1处于纹理图像本身上，我们直接使用对应位置位置的颜色数据；而对于位置2来说，它超出了纹理本身的尺寸，此时应该渲染什么颜色呢？这就涉及到了几种方式：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;方式1：任何在纹理外的纹理坐标将返回离纹理边缘最近的像素的颜色。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;方式2：当纹理坐标超过纹理的尺寸时，纹理将重复。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;方式3：类似于方式1，但图像在越过边界时将翻转。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;对于Wgpu中的配置枚举来说，方式1、2、3分别对应配置：&lt;code&gt;ClampToEdge&lt;&#x2F;code&gt;、&lt;code&gt;Repeat&lt;&#x2F;code&gt;、&lt;code&gt;MirrorRepeat&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;上面3种方式对应的效果如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在我们的示例代码中，我们使用的是&lt;code&gt;ClampToEdge&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;当然，&lt;code&gt;SamplerDescriptor&lt;&#x2F;code&gt;中不止上述的&lt;code&gt;address_mode_*&lt;&#x2F;code&gt;字段配置，但考虑到更多的配置涉及的知识点比较复杂，就不在本文中详细讲了。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chuang-jian-bang-ding-zu-yu-bang-ding-zu-bu-ju&quot;&gt;创建绑定组与绑定组布局&lt;&#x2F;h2&gt;
&lt;p&gt;本章目前为止，我们已经创建了两个资源：1）带有图像数据的纹理资源；2）采样器资源。资源创建完成以后，我们还需要通过一定的配置以便在渲染管线中访问并使用它们。具体来讲，我们需要创建一个名为绑定组（BindGroup）的对象。绑定组核心是描述了一组资源绑定关系，单个资源绑定关系其实就是代表了与某个资源的关联关系，绑定组则是这些关联关系的成组描述。让我们接下来通过实践来理解它。&lt;&#x2F;p&gt;
&lt;p&gt;创建一个绑定组总共需要两步：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;创建一个绑定组布局（BindGroupLayout）对象&lt;&#x2F;li&gt;
&lt;li&gt;基于绑定组布局来创建一个绑定组对象&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;110.png&quot; alt=&quot;110&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;接下来，让我们具体分析每一步中的配置参数。首先是创建 &lt;strong&gt;绑定组布局&lt;&#x2F;strong&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;120.png&quot; alt=&quot;120&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;从图中代码我们可以看到，创建绑定组布局，核心就是设置&lt;code&gt;BindGroupLayoutDescriptor&lt;&#x2F;code&gt;中的&lt;code&gt;entries&lt;&#x2F;code&gt;字段，该字段是一个由&lt;code&gt;BindGroupLayoutEntry&lt;&#x2F;code&gt;对象构成的数组。对于&lt;code&gt;BindGroupLayoutEntry&lt;&#x2F;code&gt;来说，它包含了以下四个字段：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;binding&lt;&#x2F;li&gt;
&lt;li&gt;visibility&lt;&#x2F;li&gt;
&lt;li&gt;ty&lt;&#x2F;li&gt;
&lt;li&gt;count&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;对于&lt;code&gt;binding&lt;&#x2F;code&gt;字段，它代表了某个具体的&lt;strong&gt;绑定&lt;&#x2F;strong&gt;在整个绑定组中的位置插槽。要理解这句话，我们需要回过头来先理解什么&lt;strong&gt;绑定组布局&lt;&#x2F;strong&gt;，绑定组布局的本质是通过配置来描述一堆“绑定资源”的组成形态。我们可以将绑定组布局比喻成一个包含多个格子的盒子：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;130.png&quot; alt=&quot;130&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于这样一个盒子，其中每一个小格子都会有一个“编号”，这个“编号”其实就是&lt;code&gt;binding&lt;&#x2F;code&gt;字段的形象表达。有了这一层思维以后，就不难理解关于&lt;code&gt;BindGroupLayoutEntry&lt;&#x2F;code&gt;其余字段的作用了，其实就是描述了对应格子中存放的绑定资源对象的属性。&lt;&#x2F;p&gt;
&lt;p&gt;对于&lt;code&gt;visibility&lt;&#x2F;code&gt;字段，它表示了对应&lt;code&gt;binding&lt;&#x2F;code&gt;下的绑定资源在着色器中的哪个阶段可见。这个字段可以是&lt;code&gt;VERTEX&lt;&#x2F;code&gt;（即顶点着色器阶段可以见）、&lt;code&gt;FRAGMENT&lt;&#x2F;code&gt;（即片元着色器阶段可以见）等，此外，你也可以使用&lt;code&gt;VERTEX | FRAGMENT&lt;&#x2F;code&gt;来表示顶点着色器阶段和片元着色器同时可见。“可见”意味着只有在&lt;strong&gt;对应阶段&lt;&#x2F;strong&gt;着色器代码中运行中，我们才能够通过一定的方式来正确访问某些资源。&lt;&#x2F;p&gt;
&lt;p&gt;对于&lt;code&gt;ty&lt;&#x2F;code&gt;字段，它代表了这个插槽位置下的资源是什么类型的。比如是一个纹理资源，或是一个采样器资源。请注意，这里依然是定义，即描述这个位置将会是个什么类型的资源，而并不是实际的资源数据，因为实际的资源要等到创建绑定组对象本身的时候才会设置。&lt;&#x2F;p&gt;
&lt;p&gt;对于&lt;code&gt;count&lt;&#x2F;code&gt;字段，我们暂时不进行详细的描述，以后会提到。&lt;&#x2F;p&gt;
&lt;p&gt;在了解了&lt;code&gt;BindGroupLayoutEntry&lt;&#x2F;code&gt;每一个字段意义以后，让我们回过头来看一看创建 &lt;strong&gt;绑定组布局&lt;&#x2F;strong&gt; 的代码，其含义就比较容易理解了。即，我们创建了一个绑定组布局，这个绑定组布局定义了接下来即将创建的绑定组的形态：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;由2个&lt;strong&gt;绑定资源&lt;&#x2F;strong&gt;组成，插槽位置分别是&lt;code&gt;0&lt;&#x2F;code&gt;和&lt;code&gt;1&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;2个绑定资源均在片元着色器阶段可见，能被使用&lt;&#x2F;li&gt;
&lt;li&gt;2个绑定资源的类型分别是纹理和采样器（具体的配置这里就不详细描述了，读者直接使用代码中的配置即可）&lt;&#x2F;li&gt;
&lt;li&gt;两个绑定组元素的count均为None（读者暂时不用关心意义）&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;理解了绑定组布局配置后，再让我们聚焦 &lt;strong&gt;绑定组&lt;&#x2F;strong&gt; 本身的创建。具体代码如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;140.png&quot; alt=&quot;140&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;相信结合了前面绑定组布局的介绍，读者这里应该很容易的看出。首先，我们创建绑定组本身的时候，使用了刚刚创建的绑定组布局。其次，对于&lt;code&gt;entries&lt;&#x2F;code&gt;字段，&lt;strong&gt;我们也同样设置了2项&lt;&#x2F;strong&gt;。其中第1项的&lt;code&gt;binding&lt;&#x2F;code&gt;为&lt;code&gt;0&lt;&#x2F;code&gt;，代表了我们配置的绑定资源对应前面绑定组布局中&lt;code&gt;binding&lt;&#x2F;code&gt;为&lt;code&gt;0&lt;&#x2F;code&gt;的配置定义，&lt;code&gt;resource&lt;&#x2F;code&gt;字段则设置了一个由纹理对象而来的纹理视图对象，即插槽为0位置的绑定资源关联的就是我们的一个纹理视图资源，（纹理视图是对纹理数据本身访问的一层抽象）；对于第2项，其&lt;code&gt;binding&lt;&#x2F;code&gt;值为&lt;code&gt;1&lt;&#x2F;code&gt;，代表了我们配置的绑定资源对应前面绑定组布局中&lt;code&gt;binding&lt;&#x2F;code&gt;为&lt;code&gt;1&lt;&#x2F;code&gt;的配置定义，其&lt;code&gt;resource&lt;&#x2F;code&gt;字段引用了创建的采样器sampler，代表我们关联了一个创建好的采样器资源。当然，我们用一张图来描述可能更加直观：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;150.png&quot; alt=&quot;150&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Ok，至此我们完成了一个绑定组的创建工作。绑定组本身已经包含有具体的资源数据，需要在运行时使用，因此我们将其保存至&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;的&lt;code&gt;bind_group&lt;&#x2F;code&gt;字段中。并在&lt;code&gt;draw&lt;&#x2F;code&gt;方法中合适位置进行设置：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;160.png&quot; alt=&quot;160&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上述代码设置完成以后，其实还不够，为了能够使用到绑定组（及其关联的纹理、采样器资源）。我们还需要对一个比较久远的对象的创建过程进行调整。在很久之前，我们曾创建了渲染管线RenderPipeline这一对象：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;170.png&quot; alt=&quot;170&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;chuang-jian-guan-xian-bu-ju&quot;&gt;创建管线布局&lt;&#x2F;h2&gt;
&lt;p&gt;为了能够真正地使用到绑定组，我们需要创建一个名为管线布局（PipelineLayout）的对象，同时将绑定组布局设置到管线布局中，以便关联到我们创建的渲染管线中。首先，我们先通过创建好的绑定组布局对象来创建管线布局对象：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;180.png&quot; alt=&quot;180&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;然后，我们适当的调整原来的&lt;code&gt;create_pipeline&lt;&#x2F;code&gt;的调用点，将其移动到PipelineLayout对象创建以后：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;190.png&quot; alt=&quot;190&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;接着，我们修改&lt;code&gt;create_pipeline&lt;&#x2F;code&gt;的方法签名，让其传入一个管线布局对象，并在调用处将管线布局对象传入：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;200.png&quot; alt=&quot;200&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;最后，我们修改&lt;code&gt;create_pipeline&lt;&#x2F;code&gt;的具体实现，在调用&lt;code&gt;create_render_pipeline&lt;&#x2F;code&gt;传递的&lt;code&gt;RenderPipelineDescriptor&lt;&#x2F;code&gt;的&lt;code&gt;layout&lt;&#x2F;code&gt;字段，设置管线布局：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;210.png&quot; alt=&quot;210&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这几步下来，最重要的结果是，我们将原本一个“光秃秃”的没有关联任何资源的渲染管线，修改为了关联了一个管线布局的渲染管线，而这个渲染管线关联的管线布局又包含一个绑定组布局，绑定组布局中还定义两个绑定资源：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;220.png&quot; alt=&quot;220&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;同时，读者还需要仔细辨别和理解，在&lt;code&gt;draw&lt;&#x2F;code&gt;方法调用时（即运行时），里面使用到的是关联了实际资源数据的 &lt;strong&gt;绑定组&lt;&#x2F;strong&gt;，而不是 &lt;strong&gt;绑定组布局&lt;&#x2F;strong&gt;。在创建的渲染管线的时候，则都是布局（包含绑定组布局的管线布局）。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xiu-gai-ding-dian-shu-ju&quot;&gt;修改顶点数据&lt;&#x2F;h2&gt;
&lt;p&gt;接下来，我们还需要对原有的顶点数据的部分进行修改。首先，我们修改&lt;code&gt;vertex.rs&lt;&#x2F;code&gt;文件中定义的&lt;code&gt;Vertex&lt;&#x2F;code&gt;结构体，将原本的&lt;code&gt;color&lt;&#x2F;code&gt;字段改为&lt;code&gt;tex_uv&lt;&#x2F;code&gt;（表示UV二维坐标），类型也由原来的&lt;code&gt;[f32; 3]&lt;&#x2F;code&gt;改为了&lt;code&gt;[f32; 2]&lt;&#x2F;code&gt;（二维坐标）；与之对应的，就是下方的&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;的数据也要适当调整：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;230.png&quot; alt=&quot;230&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;这里的tex_uv值的含义考虑到篇幅原因，就不在本文中进行介绍了。感兴趣的读者可以等笔者后续的文章，或自行学习&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;然后，我们需要对该文件中曾编写的方法&lt;code&gt;create_vertex_buffer_layout&lt;&#x2F;code&gt;的具体实现进行调整。对于&lt;code&gt;shader_location&lt;&#x2F;code&gt;为&lt;code&gt;1&lt;&#x2F;code&gt;的地方，原本每一个顶点数据的该位置是一个&lt;code&gt;[f32; 3]&lt;&#x2F;code&gt;的数据（表示一个颜色数据），由于调整为了&lt;code&gt;[f32; 2]&lt;&#x2F;code&gt;（表示一个UV坐标数据），因此我们需要在做出对应的修改：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;240.png&quot; alt=&quot;240&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;修改完成后，关于Rust中的代码我们基本上就编写完成了。接下来我们还差最后一步，修改着色器代码。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xiu-gai-zhao-se-qi-dai-ma&quot;&gt;修改着色器代码&lt;&#x2F;h2&gt;
&lt;p&gt;首先，对于顶点着色器的部分，由于我们传入管线的数据实际上是有调整的（Vertex结构体字段调整），因此着色器中的代码也需要完成对应调整：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;250.png&quot; alt=&quot;250&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;关于着色器代码中的&lt;code&gt;VertexInput&lt;&#x2F;code&gt;、&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;、&lt;code&gt;FragmentInput&lt;&#x2F;code&gt;的对应关系我就不多赘述了。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;接下来，重要的一步，在着色器代码中，我们需要加入有关纹理资源以及采样器资源对象的代码，并修改片元着色器的实现逻辑：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;260.png&quot; alt=&quot;260&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在这里有两个要点需要解释。首先我们会看到我们新加入了两个“变量”，类型分别是&lt;code&gt;texture_2d&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;和&lt;code&gt;sampler&lt;&#x2F;code&gt;（这两个类型是wgsl内置的），分别代表了纹理对象和采样器资源对象；其次，它们的上面都有注解，有的读者可能已经能联想到这里的&lt;code&gt;group(0)&lt;&#x2F;code&gt;和&lt;code&gt;bind(0)&lt;&#x2F;code&gt;、&lt;code&gt;bind(1)&lt;&#x2F;code&gt;的含义了。对于&lt;code&gt;group(0)&lt;&#x2F;code&gt;，其实就对应了我们刚刚创建的&lt;strong&gt;渲染管线布局&lt;&#x2F;strong&gt;中定义的仅有的索引为0的绑定组布局：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;270.png&quot; alt=&quot;270&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;而&lt;code&gt;bind(0)&lt;&#x2F;code&gt;和&lt;code&gt;bind(1)&lt;&#x2F;code&gt;其实就是这个绑定组中的两个entry：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;280.png&quot; alt=&quot;280&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;再看修改后的片元着色器实现，我们调用了wgsl语言内置的&lt;code&gt;textureSample&lt;&#x2F;code&gt;方法，第一个参数是纹理对象，第二个参数是采样器对象，第三个参数是对应的uv坐标。这段代码表达了这样一个意图：对于片元的某一位置的像素点的颜色值，我们使用采样器在纹理采样得到对应的颜色。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xiao-guo-cheng-xian&quot;&gt;效果呈现&lt;&#x2F;h1&gt;
&lt;p&gt;终于，我们经历“千辛万苦”，终于完成了为了在Wgpu中渲染一张图片的所有准备工作。此时当我们运行本章程序，会得到如下的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2025-01-16&#x2F;290.png&quot; alt=&quot;290&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;我们将本章最开始的一张图片渲染到了一个正六边形中。当然，我们会发现图片的渲染实际上还存在一些问题，比如六边形内部的图片是颠倒的、图片存在扭曲、部分区域分割的不正常。这一块的内容，其实涉及到了顶点、UV坐标的一些关系处理，考虑到本章篇幅原因（内容实在太多了= _ =），就不再本章中进行详解了，笔者会单独写一篇文章来详细讲解这块的内容。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本章内容中，我们先创建了纹理以及采样器资源，并通过&lt;strong&gt;绑定组&lt;&#x2F;strong&gt;来完成了对纹理资源和采样器资源的关联，同时又介绍了如何将渲染管线和绑定组进行关联逻辑流程。本章内容很多，需要读者慢慢消化，但是相信读者在阅读本文内容以后，能够对Wgpu中的常常提到的布局Layout这一概念有一个更加深入的认识。&lt;&#x2F;p&gt;
&lt;p&gt;本章的代码仓库在这里：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;tree&#x2F;main&#x2F;ch05_texture_and_bind_group&quot;&gt;ch05_texture_and_bind_group&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;后续文章的相关代码也会在该仓库中添加，所以感兴趣的读者可以点个star，谢谢你们的支持！&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Wgpu图文详解（04）顶点索引及其缓冲区</title>
        <published>2024-12-11T00:00:00+00:00</published>
        <updated>2024-12-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Wgpu图文详解（04）顶点索引及其缓冲区/"/>
        <id>https://zhen.wang/article/Wgpu图文详解（04）顶点索引及其缓冲区/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Wgpu图文详解（04）顶点索引及其缓冲区/">&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;在上一节中我们重点介绍了图形学工程中的缓冲区Buffer的概念，同时通过大量的图解和代码实例来讲解如何构建一个顶点缓冲区，通过与着色器代码的配合，最终实现了一个渐变效果三角形渲染。相信读者还记得我们曾经编写过这样的代码：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在上述代码中，我们用了三个Vertex结构体数据分别描述三个顶点在几何空间中的位置以及颜色。当然，在实际应用场景中，我们几乎不可能只是渲染一个三角形，我们不可避免的会渲染一些复杂的图形。值得提到的是，无论是2D图形还是3D图形，在图形学中通常都会通过一定的算法将其拆解为n个独立的三角面：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;因此，为了能够表达更加复杂的图形，我们会增加三角形的数量。例如，在本文中，我们尝试渲染如下一个六边形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;为了实现上述的效果，我们可以将这个六边形分解为如下的4个三角面：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;其中，由a、b、c、d、e、f六个点构成一个六边形；abc构成三角面A，acd构成三角面B，ade构成三角面C，aef构成三角面D。&lt;&#x2F;p&gt;
&lt;p&gt;按照之前的思路，我们可以在顶点数据列表中，增加顶点：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;只是修改VRETEXT_LIST的内容，在前一篇文章的基础上，我们就可以渲染一个期望的六边形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;上述内容代码已经作为一次提交推送到&lt;code&gt;wgpu_winit_example&lt;&#x2F;code&gt;仓库中，对应提交记录：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;commit&#x2F;4e60c76151b331397c1e1b66ad70f4405747b5f9&quot;&gt;Commit 4e60c76&lt;&#x2F;a&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;ding-dian-suo-yin-ji-qi-huan-chong-qu&quot;&gt;顶点索引及其缓冲区&lt;&#x2F;h1&gt;
&lt;p&gt;尽管此时我们已经完成了一个六边形的渲染，但同时我们会发现，对于相邻的三角面会共享一些顶点，例如三角面A和B都共享了顶点a、c：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;通过梳理不难发现，除了顶点b、f外，另外的4个顶点：a、c、d、e，都不止一次被共享使用。假设存在一个场景会有成千上万个三角面，试想一下，如果能够复用顶点数据，那将会节省大量的内存空间。因此，有没有一种方式能够高效的复用顶点呢？答案是肯定的——使用&lt;strong&gt;顶点索引&lt;&#x2F;strong&gt;来表达三角面。&lt;&#x2F;p&gt;
&lt;p&gt;对于上面的六边形，首先，我们只需要将每一个顶点定义出来：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; a, b, c, d, e, f
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;VERTEX_LIST&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;[Vertex] = &amp;amp;[
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; a
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;], color: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; b
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [-&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.3&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;], color: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; c
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [-&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.3&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;], color: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; d
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;], color: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; e
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.3&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;], color: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; f
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.3&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;], color: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;然后，在通过&lt;strong&gt;顶点索引&lt;&#x2F;strong&gt;表达：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;INDEX_LIST&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u16&lt;&#x2F;span&gt;&lt;span&gt;] = &amp;amp;[
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; abc
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; acd
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ade
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; aef
&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;INDEX_LIST中每一个整数就对应VERTEX_LIST数组中对应的索引位置&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;此时，我们的项目代码中的改动就只有vertex.rs文件中的改动：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;讲一个题外话，此时你可以尝试运行基于上述改动的代码，会发现得到如下的一个效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;如果读者对之前的内容掌握了，其实也不难理解呈现这个效果的原因。按照之前对于wgpu的图元配置（&lt;code&gt; wgpu::PrimitiveTopology::TriangleList&lt;&#x2F;code&gt;，请读者自行复习相关概念、代码），会让wgpu按照每三个顶点作为一个三角面来渲染，同时我们将顶点改为了6个，正好构成两个三角面（abc、def）。&lt;&#x2F;p&gt;
&lt;p&gt;回到正题，我们创建了一个顶点索引数据，其目的就是告诉gpu：“在接下来请将传入的6个顶点数据，依次按照顶点索引数组的配置，渲染4个三角形“。那么我们应该如何把顶点索引数据传递给gpu（渲染管线），并让gpu能够理解并消费顶点索引数据呢？其实过程和前面我们将顶点数据创建、消费是很类似的。&lt;&#x2F;p&gt;
&lt;p&gt;首先，顶点索引数据既然是要交给gpu，那么依然离不开&lt;strong&gt;缓冲区&lt;&#x2F;strong&gt;这一重要概念，不过与顶点缓冲区的区别在于我们要为缓冲区指明其类型为顶点索引缓冲区：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于上述代码。首先，我们参考之前方式，将顶点索引数组数据通过工具库bytemuck转换为字节数据。值得注意的是，这里的&lt;strong&gt;VERTEX_IDNEX_LIST&lt;&#x2F;strong&gt;的类型是&lt;code&gt;&amp;amp;[u16]&lt;&#x2F;code&gt;，即每一个元素是&lt;code&gt;u16&lt;&#x2F;code&gt;基本类型，因此不需要像之前顶点Vertex结构体那样为其实现&lt;code&gt;bytemuck::Zeroable&lt;&#x2F;code&gt;和&lt;code&gt;bytemuck::Pod&lt;&#x2F;code&gt;trait，就可以转为字节数据。&lt;&#x2F;p&gt;
&lt;p&gt;然后，我们同样通过device的&lt;code&gt;create_buffer_init&lt;&#x2F;code&gt;API来创建一个缓冲区实例，并传入字节数据，不过这里的&lt;code&gt;usage&lt;&#x2F;code&gt;字段我们需要传入枚举&lt;code&gt;wgpu::BufferUsage::INDEX&lt;&#x2F;code&gt;来表明创建的缓冲区是用来存放&lt;strong&gt;顶点索引&lt;&#x2F;strong&gt;数据，而不是其他类型的数据。&lt;&#x2F;p&gt;
&lt;p&gt;最后，我们按照之前模式一样，创建并存放顶点缓冲区数据：同样在WgpuCtx结构体中增加&lt;code&gt;vertex_index_buffer&lt;&#x2F;code&gt;字段来存放我们创建的顶点索引缓冲区数据。&lt;&#x2F;p&gt;
&lt;p&gt;至此，我们就完成了一个&lt;strong&gt;顶点索引&lt;&#x2F;strong&gt;缓冲区的创建工作了。那么接下来就是在渲染时消费到这个缓冲区数据。具体代码如下所示：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;110.png&quot; alt=&quot;110&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在原来&lt;code&gt;WgpuCtx::draw&lt;&#x2F;code&gt;方法代码基础上，我们增加了上述两行代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 消费存放的 vertex_index_buffer
&lt;&#x2F;span&gt;&lt;span&gt;rpass.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;set_index_buffer&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.vertex_index_buffer.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;slice&lt;&#x2F;span&gt;&lt;span&gt;(..), wgpu::IndexFormat::Uint16); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 1.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 调用draw_indexed，传入对应数量的顶点数量
&lt;&#x2F;span&gt;&lt;span&gt;rpass.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;draw_indexed&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;VERTEX_INDEX_LIST&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;len&lt;&#x2F;span&gt;&lt;span&gt;() as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;首先，调用渲染通道RenderPass的&lt;code&gt;set_index_buffer&lt;&#x2F;code&gt;API，传入我们存放的顶点索引缓冲实例的切片以及对应的数据类型格式（因为我们的每一个顶点的数据是&lt;code&gt;u16&lt;&#x2F;code&gt;，因此这里用枚举&lt;code&gt;wgpu::IndexFormat::Uint16&lt;&#x2F;code&gt;）；&lt;&#x2F;p&gt;
&lt;p&gt;其次，调用&lt;code&gt;draw_indexed&lt;&#x2F;code&gt;API来传入具体顶点索引数组的长度，以及要从数组中哪个位置开始作为第一个索引（这里我们填入0），最后一个参数我们默认填入&lt;code&gt;0..1&lt;&#x2F;code&gt;表明只有一个实例。&lt;&#x2F;p&gt;
&lt;p&gt;完成上述代码以后，我们再次运行项目，会发现得到了我们希望的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-12-11&#x2F;120.png&quot; alt=&quot;120&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;实际上，无论是上一篇的顶点缓冲区还是本文的索引缓冲区。我们的核心思路都是创建一些能够表达信息的数据，再基于这些数据创建缓冲区实例。缓冲区通过类型来区分其作用。因此，后续的内容中如果出现了其他类型缓冲区，我相信读者能够理解对应的思路。&lt;&#x2F;p&gt;
&lt;p&gt;读者如果看过&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sotrh.github.io&#x2F;learn-wgpu&#x2F;&quot;&gt;《learn wgpu》&lt;&#x2F;a&gt;的内容，会发现在《learn wgpu》中是将顶点缓冲区、顶点索引等内容放到了&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;sotrh.github.io&#x2F;learn-wgpu&#x2F;beginner&#x2F;tutorial4-buffer&#x2F;&quot;&gt;一篇&lt;&#x2F;a&gt;中，但是笔者在编写的时候考虑到在上一篇（《Wgpu图文详解（03）缓冲区Buffer》）内容比较多，将顶点索引这块的内容再放进去会增加读者的阅读负担，因此将顶点索引的部分单独拆到了这一章中。同时，也是希望读者能够将本文作为对上一篇文章缓冲区内容的巩固。&lt;&#x2F;p&gt;
&lt;p&gt;本章的代码仓库在这里：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;tree&#x2F;main&#x2F;ch04_vertex_index_buffer&quot;&gt;ch04_vertex_index_buffer&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;后续文章的相关代码也会在该仓库中添加，所以感兴趣的读者可以点个star，谢谢你们的支持！&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Wgpu图文详解（03）顶点及其缓冲区</title>
        <published>2024-11-18T00:00:00+00:00</published>
        <updated>2024-11-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Wgpu图文详解（03）顶点及其缓冲区/"/>
        <id>https://zhen.wang/article/Wgpu图文详解（03）顶点及其缓冲区/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Wgpu图文详解（03）顶点及其缓冲区/">&lt;p&gt;在上一篇文章中，我们介绍了Wgpu中的渲染管线与着色器的概念以及基本用法。相信读者还记得，我们在渲染一个三角形的时候，使用了三角形的三个&lt;strong&gt;顶点的索引&lt;&#x2F;strong&gt;作为了顶点着色器的输入，并根据索引值计算了三个几何顶点在视口中的位置，并通过片元着色器的代码逻辑，控制了每一个像素都用红色色值，最终渲染了一个红色三角形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;当然，我们不可能一直使用wgpu来渲染这样的简单固定的图形。面对实际的场景，我们有时候需要根据一些上下文来动态的修改渲染图形的大小形状。在本文中，我们将开始介绍顶点缓冲区的概念，来为后续实际的场景做一些铺垫。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ren-shi-huan-chong-qu&quot;&gt;认识缓冲区&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;strong&gt;缓冲区&lt;&#x2F;strong&gt;（Buffer）一个可用于 GPU 操作的内存块（又叫“显存”）。在wgpu（或其他例如OpenGL等库）中的缓冲区概念通常指的是 GPU 能读写的内存区域，与之对应的就是我们常见的CPU内存。回想一下常规的软件运行的过程：程序在启动后，会在“内存”中申请一块能够存放数据的区域。在运行的过程中，我们的代码指令按照既定的逻辑做着计算，并不断的读、写内存区域里面的数据，以达到期望的程序运行的结果。&lt;strong&gt;不严谨地讲&lt;&#x2F;strong&gt;，GPU 与 CPU 是一样的，它同样能够执行计算逻辑，同样会有数据存储的区域，这个区域就是 GPU 的缓冲区。&lt;&#x2F;p&gt;
&lt;p&gt;一般来说，我们都在 CPU 直接阶段，在内存中将一些初始的数据准备好，通过一定的方式发送给 GPU，并存储在GPU上的缓冲区中。在执行的过程中，我们可以通过着色器代码来读取缓冲区中的数据：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chuang-jian-ding-dian-huan-chong-qu&quot;&gt;创建顶点缓冲区&lt;&#x2F;h1&gt;
&lt;p&gt;为了更好的管理不同类型的数据（比如常见的有顶点数据、顶点索引数据），我们会按照其不同类型来设定不同的缓冲区。在本文中，我们先介绍如何创建并使用顶点缓冲区，对于其他缓冲区我们会在后续的文章中说明。&lt;&#x2F;p&gt;
&lt;p&gt;顶点缓冲区，顾名思义，就是包含了在渲染过程中会使用到的&lt;strong&gt;顶点数据&lt;&#x2F;strong&gt;的 GPU 显存区域。需要注意的是，图形学中的顶点并不是我们常规意义上的几何顶点，而是包含了位置坐标、颜色信息、纹理坐标以及法线向量等的顶点数据，常规意义上的几何顶点&lt;strong&gt;仅仅&lt;&#x2F;strong&gt;是顶点数据中的一部分。&lt;&#x2F;p&gt;
&lt;p&gt;在上一篇文章中，尽管在最后我们成功终绘制了一个三角形，但实际上它的三个顶点位置是通过三个顶点索引（0、1、2）计算而来的。假设我期望绘制一个比较另类的三角形或其他图形，纯粹靠顶点索引是不够的。这种场景我们一般会按照如下的方式进行：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;准备一些包含自定义位置信息的顶点数据；&lt;&#x2F;li&gt;
&lt;li&gt;将顶点数据放置到顶点缓冲区中，并进行一定的配置；&lt;&#x2F;li&gt;
&lt;li&gt;最后，在着色器代码中通过一定的方式读取这些顶点数据，并交给顶点着色器来使用。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;接下来让我们开始实践如何通过编程方式创建顶点缓冲区。&lt;&#x2F;p&gt;
&lt;p&gt;假设最终我们期望渲染一个由&lt;code&gt;(0, 1)&lt;&#x2F;code&gt;、&lt;code&gt;(-0.5, -0.5)&lt;&#x2F;code&gt;、&lt;code&gt;(0.5, 0)&lt;&#x2F;code&gt;三个2维顶点构成的三角形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;首先，让我们在基础项目中增加一个结构体Vertex，用来表达我们的顶点：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这个结构体我们现在仅有一个类型为&lt;code&gt;[f32; 3]&lt;&#x2F;code&gt;类型的字段&lt;code&gt;position&lt;&#x2F;code&gt;，用来表示一个位置坐标。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️这里务必添加Copy派生&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;yin-shen-guan-yu-nei-cun-bu-ju&quot;&gt;引申：关于内存布局&lt;&#x2F;h2&gt;
&lt;p&gt;该结构体上的属性，除了我们常见用来派生&lt;code&gt;Copy&lt;&#x2F;code&gt;、&lt;code&gt;Clone&lt;&#x2F;code&gt;等trait的&lt;code&gt;derive&lt;&#x2F;code&gt;属性外，还有一个特殊的属性：&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;，在配置该属性后，Rust 编译器会强制&lt;strong&gt;按照 C 编译器&lt;&#x2F;strong&gt;的编译方式来安排结构体字段的顺序和&lt;strong&gt;对齐方式&lt;&#x2F;strong&gt;。假设有如下结构体，在&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;的加持下，其内存布局会保持4字节对齐：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上面的结构体中，age字段的类型是u8，但因为强制使用了&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;，让其保持了4字节的内存布局。我们可以用如下的代码来验证：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;当然，有的小伙伴会发现即使不添加&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;，结果也是24bytes，是因为Rust编译器在某些场景下会进行对齐，&lt;strong&gt;不过这样无法保证是按照和C编译器一样的4字节对齐&lt;&#x2F;strong&gt;；此外，Rust编译器在有时为了内存的高效利用，可能会进行布局压缩。当然，你还可以使用&lt;code&gt;#[repr(packed)]&lt;&#x2F;code&gt;来禁用内存对齐填充：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;好了，让我们回归正文。此时我们已经编写了一个&lt;code&gt;Vertex&lt;&#x2F;code&gt;结构体，也理解了&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;的意义。接下来，我们创建一个数组切片来存放三个顶点的数据：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 表示三角形三个顶点的顶点列表
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;VERTEX_LIST&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;[Vertex] = &amp;amp;[
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [-&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;    Vertex { position: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.5&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0.0&lt;&#x2F;span&gt;&lt;span&gt;] },
&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在编写了三个顶点的数据后，我们更进一步，将顶点缓冲区创建出来消费顶点数据。&lt;&#x2F;p&gt;
&lt;p&gt;首先，让我们在&lt;code&gt;async_new&lt;&#x2F;code&gt;方法中的合适位置通过调用Device实例的&lt;code&gt;create_buffer_init&lt;&#x2F;code&gt;方法创建一个顶点缓冲区对象：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;contents&lt;&#x2F;code&gt;字段需要我们提供&lt;code&gt;&amp;amp;[u8]&lt;&#x2F;code&gt;类型的数据，即字节数组的切片引用，这里我们先传空，待会儿会讲到如何将我们的&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;数据转为&lt;code&gt;&amp;amp;[u8]&lt;&#x2F;code&gt;类型的数据；&lt;code&gt;usage&lt;&#x2F;code&gt;字段我们现在传入&lt;code&gt; wgpu::BufferUsages::VERTEX&lt;&#x2F;code&gt;这个枚举，表明我们要创建的是一个顶点缓冲区，而不是其他的缓冲区。&lt;&#x2F;p&gt;
&lt;p&gt;接下来，我们尝试将前面创建的&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;数据转换为&lt;code&gt;&amp;amp;[u8]&lt;&#x2F;code&gt;字节数据。这里我们使用一个工具库&lt;code&gt;bytemuck&lt;&#x2F;code&gt;，该库可以方便的将我们的一些数据结构转为内存中的字节数据。其具体方式如下：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;在依赖中添加&lt;code&gt;bytemuck&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;修改&lt;code&gt;Vertex&lt;&#x2F;code&gt;结构体的内容：&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;在创建缓冲区的地方添加如下的转换代码：&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;调用bytemuck的cast_slice方法，将原始数据转为u8的切片，并作为contents字段的值传入&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;修改&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;结构体，保存我们本次创建的顶点缓冲区实例：&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;110.png&quot; alt=&quot;110&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;总结一下，为了创建一个顶点缓冲区，我们经历如下几步：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;定义一个结构体（&lt;code&gt;Vertex&lt;&#x2F;code&gt;）来表示一个顶点数据，该结构体除开配置&lt;code&gt;#[derive(Copy, Clone)]&lt;&#x2F;code&gt;属性外，还需要使用&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;来保证该结构体在编译后的内存布局及对齐字节数据保持和C编译器一样；以及，让结构体实现来自&lt;code&gt;bytemuck&lt;&#x2F;code&gt;库提供的&lt;code&gt;Pod&lt;&#x2F;code&gt;和&lt;code&gt;Zeroable&lt;&#x2F;code&gt;两个trait，以供后续通过&lt;code&gt;bytemuck&lt;&#x2F;code&gt;的提供的API来将数据转为&lt;code&gt;&amp;amp;[u8]&lt;&#x2F;code&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;完成&lt;code&gt;Vertex&lt;&#x2F;code&gt;结构体的定义后，我们又根据最终想要渲染的三角形的几何结构，使用&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;来存储了三个顶点数据。&lt;&#x2F;li&gt;
&lt;li&gt;使用&lt;code&gt;bytemuck&lt;&#x2F;code&gt;提供的API将&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;通过将其转为了u8字节数组切片字节数据。&lt;&#x2F;li&gt;
&lt;li&gt;调用Device提供的API&lt;code&gt;create_buffer_init&lt;&#x2F;code&gt;，传入顶点数组字节数据，以创建一个顶点缓冲区实例。&lt;&#x2F;li&gt;
&lt;li&gt;将顶点缓冲区实例存储到WgpuCtx实例，以供后续消费使用。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;至此，对于创建顶点缓冲区部分的介绍就到此为止。接下来我们需要介绍另一个同样重要的内容：&lt;strong&gt;顶点缓冲区布局（VertexBufferLayout）&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chuang-jian-ding-dian-huan-chong-qu-bu-ju&quot;&gt;创建顶点缓冲区布局&lt;&#x2F;h1&gt;
&lt;p&gt;首先，我们需要明白为什么会有&lt;strong&gt;缓冲区布局&lt;&#x2F;strong&gt;这一东西。假设现在在 GPU 显存中有如下的一段数据：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;120.png&quot; alt=&quot;120&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在没有其他上下文的情况下，我们无法理解这段内存中的数据有何意义。同样的，如果我们单是把先前创建的顶点数据放入顶点缓冲区中，在实际渲染的过程中，GPU 也无法理解这一堆的二进制数据应该如何使用。此时，我们就需要用一些配置上下文来解释顶点缓冲区中的数据的具体意义。&lt;&#x2F;p&gt;
&lt;p&gt;还是以上图数据为例，如果现在告诉你这是一段包含了&lt;strong&gt;3&lt;&#x2F;strong&gt;个顶点数据的内存布局，其步进（stride）是3字节（即每三个字节就算做一个顶点数据）；同时，单看每一份顶点数据，按照从其&lt;strong&gt;偏移字节为0&lt;&#x2F;strong&gt;的地方开始是一份位置数据，其类型为3个float32（32bits，即4bytes）数据，现在对于这段内存中的数据的布局结构是不是变的比较清晰了呢：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;130.png&quot; alt=&quot;130&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;有了上述的说明，再回过头来就不难理解缓冲区布局的意义了。接下来就让我们通过代码实践来定义一个顶点缓冲区布局实例。&lt;&#x2F;p&gt;
&lt;p&gt;首先，我们依然在&lt;code&gt;vertex.rs&lt;&#x2F;code&gt;文件中增加一个方法，用来返回一个顶点缓冲区布局实例：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;140.png&quot; alt=&quot;140&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于该方法的实现，我们就是返回了如下的一个结构体：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;wgpu::VertexBufferLayout {
&lt;&#x2F;span&gt;&lt;span&gt;    array_stride: size_of::&amp;lt;Vertex&amp;gt;() as wgpu::BufferAddress,
&lt;&#x2F;span&gt;&lt;span&gt;    step_mode: wgpu::VertexStepMode::Vertex,
&lt;&#x2F;span&gt;&lt;span&gt;    attributes: &amp;amp;[
&lt;&#x2F;span&gt;&lt;span&gt;        wgpu::VertexAttribute {
&lt;&#x2F;span&gt;&lt;span&gt;            offset: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            shader_location: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;            format: wgpu::VertexFormat::Float32x3,
&lt;&#x2F;span&gt;&lt;span&gt;        },
&lt;&#x2F;span&gt;&lt;span&gt;    ],
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;字段&lt;code&gt;array_stride&lt;&#x2F;code&gt;表示的就是每一份顶点数据在内存中的&lt;strong&gt;步进&lt;&#x2F;strong&gt;长度，在本例中，一个&lt;code&gt;Vertex&lt;&#x2F;code&gt;结构体在&lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt;的属性配置下能够确保是12bytes。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;字段&lt;code&gt;step_mode&lt;&#x2F;code&gt;我们暂时不详细介绍，读者可以简单理解为告诉渲染管线每一份数据代表的是一个顶点数据（），这里默认使用该枚举值&lt;code&gt;VertexStepMode::Vertex&lt;&#x2F;code&gt;即可。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;字段&lt;code&gt;attributes&lt;&#x2F;code&gt;是一个数组切片引用，在这里我们只传递了一个&lt;code&gt;VertexAttribute&lt;&#x2F;code&gt;数据，表示就目前而言，我们一份顶点数据中，只有一份有意义的“子数据”。对于这份“子数据”，我们配置了&lt;code&gt;offset&lt;&#x2F;code&gt;、&lt;code&gt;shader_location&lt;&#x2F;code&gt;以及&lt;code&gt;format&lt;&#x2F;code&gt;字段。这三个字段整体表达了这样一个事实：在一份顶点数据中，从&lt;code&gt;offset = 0&lt;&#x2F;code&gt;开始有一段格式为&lt;code&gt;Float32x3&lt;&#x2F;code&gt;（float32 = 32bits  = 4bytes， 乘以3就等于12bytes）的数据，这段数据在shader着色器上的location为0的位置。相信读者对offset和format应该能够理解，但是对于“这段数据在shader着色器上的location为0的位置”这句话还有些难以理解，别着急，我们后面会讲到的。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;xiao-fei-huan-chong-qu-ji-bu-ju&quot;&gt;消费缓冲区及布局&lt;&#x2F;h1&gt;
&lt;p&gt;总结下现状，我们首先创建了顶点缓冲区并将其作为&lt;code&gt;vertex_buffer&lt;&#x2F;code&gt;存放到了&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;实例中；同时，我们还编写一个名为&lt;code&gt;create_vertex_buffer_layout&lt;&#x2F;code&gt;的方法用来构造一个顶点缓冲区布局实例，接下来我们会使用到上面准备工作的成果了。&lt;&#x2F;p&gt;
&lt;p&gt;首先，让我们在&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;的&lt;code&gt;draw&lt;&#x2F;code&gt;方法中适当修改代码来消费顶点缓冲区：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;150.png&quot; alt=&quot;150&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在调用渲染通道（RenderPass）实例的&lt;code&gt;draw&lt;&#x2F;code&gt;方法前，我们先调用&lt;code&gt;set_vertex_buffer&lt;&#x2F;code&gt;方法。该方法接受两个参数，第一个参数slot指的是我们要把顶点缓冲区中的数据放置到显存内部的顶点缓冲区域的哪个索引位置，这里我们设置为0，表示我们会设置到默认0的位置；第二个参数使用的缓冲区的数据片段，这里我们直接消费整个顶点数据，因此代码编写为&lt;code&gt;slice(..)&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;然后，修改&lt;code&gt;draw&lt;&#x2F;code&gt;的参数传递。将原来固定的&lt;code&gt;0..3&lt;&#x2F;code&gt;（即3个顶点）修改为动态的，根据我们创建的&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;的实际长度，这样在将来我们会创建更多的顶点的时候，就能够正确对应顶点数量。&lt;&#x2F;p&gt;
&lt;p&gt;完成消费顶点缓冲区的代码编写以后，接下来我们就需要再适当的位置创建缓冲区布局实例并消费它，其具体做法是：&lt;&#x2F;p&gt;
&lt;p&gt;调用&lt;code&gt;create_vertex_buffer_layout&lt;&#x2F;code&gt;方法得到缓冲区布局实例对象；把该实例对象传递给如下&lt;code&gt;VertexState&lt;&#x2F;code&gt;的&lt;code&gt;buffers&lt;&#x2F;code&gt;字段：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;160.png&quot; alt=&quot;160&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;这个地方叫做buffers，但是实际上是要传buffer布局，maybe命名有点让人误导。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;到目前为止内容偏多，让我们通过下图做一个简单的总结：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;170.png&quot; alt=&quot;170&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xiu-gai-ding-dian-zhao-se-qi-cheng-xu&quot;&gt;修改顶点着色器程序&lt;&#x2F;h1&gt;
&lt;p&gt;上面的实践过程，我们仅仅是创建并消费了顶点缓冲区以及顶点缓冲区布局实例。然而，如果在此时运行程序代码，读者会发现窗口中依然是先前的一个撑满窗口的红色三角形。很显然，我们需要适当的修改着色器程序的代码，才能真正消费到我们在上面产生的有关顶点数据。让我们对&lt;code&gt;shader.wgsl&lt;&#x2F;code&gt;做出如下的修改：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;180.png&quot; alt=&quot;180&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;首先，我们在着色器代码中定义了一个结构体&lt;code&gt;VertexInput&lt;&#x2F;code&gt;，这个结构体包含有一个&lt;code&gt;position&lt;&#x2F;code&gt;字段，其类型为&lt;code&gt;vec3f&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;值得注意的是，这个字段有一个前置的注解&lt;code&gt;@location(0)&lt;&#x2F;code&gt;。还记得前面我们说过：“这段数据在shader着色器上的location为0的位置”这句话吗？其实这里的&lt;code&gt;location(0)&lt;&#x2F;code&gt;对应匹配的就是前面在定义顶点缓冲区布局的&lt;code&gt;shader_location&lt;&#x2F;code&gt;配置：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;190.png&quot; alt=&quot;190&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于顶点数据、顶点缓冲区布局配置以及着色器中&lt;code&gt;VertexInput&lt;&#x2F;code&gt;的结构定义，我们就可以用下图来解释它们的关系了：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;200.png&quot; alt=&quot;200&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;再看顶点着色器&lt;code&gt;vs_main&lt;&#x2F;code&gt;的部分，其入参由原来的&lt;code&gt;@builtin(vertex_index) in_vertex_index: u32&lt;&#x2F;code&gt;修改为了&lt;code&gt;vertex_in: VertexInput&lt;&#x2F;code&gt;。在每次顶点着色器运行的时候，渲染管线会结合顶点缓冲区布局配置以及每一份内存中的顶点数据，为我们构建一个&lt;code&gt;VertexInput&lt;&#x2F;code&gt;结构体实例，并传入该顶点着色器方法中。在这里我们就可以直接读取到对应的position位置字段数据并直接返回了。&lt;&#x2F;p&gt;
&lt;p&gt;而对于片元着色器，我们暂时没有任何改动。因此，在一切准备工作结束以后，让我们运行程序，会发现最终渲染的三角形确实如我们所期望的结构那样展示了：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;210.png&quot; alt=&quot;210&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;gei-ding-dian-shu-ju-jia-ru-geng-duo-de-xin-xi&quot;&gt;给顶点数据加入更多的信息&lt;&#x2F;h1&gt;
&lt;p&gt;在本文中，由于我们的顶点数据结构体&lt;code&gt;Vertex&lt;&#x2F;code&gt;中只包含了一个类型为&lt;code&gt;[f32; 3]&lt;&#x2F;code&gt;的位置数据字段，因此在设置&lt;code&gt;VertexBufferLayout&lt;&#x2F;code&gt;的&lt;code&gt;attributes&lt;&#x2F;code&gt;字段的时候，我们只传入了一个&lt;code&gt;VertexAttribute&lt;&#x2F;code&gt;配置，并且其offset为0，代表了我们的一份在内存中的顶点数据，只包含一份属性数据，且是从偏移字节为0开始的。当然，正如前面提到的，顶点数据并非只会有位置数据，通常伴随着的还会有颜色信息、法线信息等。在这里，我们尝试给顶点加入颜色数据，好在着色器处理阶段能够定制三角形的颜色。&lt;&#x2F;p&gt;
&lt;p&gt;首先，让我们尝试修改&lt;code&gt;Vertex&lt;&#x2F;code&gt;结构体，加入一个颜色字段：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;220.png&quot; alt=&quot;220&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;完成以后，可以想象到，把&lt;code&gt;VERTEX_LIST&lt;&#x2F;code&gt;数据转为字节数据放到顶点缓冲区以后，其内存布局会是如下形式：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;230.png&quot; alt=&quot;230&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;如果读者理解了前面提到的offset的含义，那么就不难想到，为了让顶点着色器能够访问到颜色信息。我们需要将顶点缓冲区布局中关于attributes字段增加一条配置：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;240.png&quot; alt=&quot;240&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;在顶点缓冲区布局对象的&lt;code&gt;attributes&lt;&#x2F;code&gt;字段，在原有基础上，再插入一份&lt;code&gt;VertexAttribute&lt;&#x2F;code&gt;配置，代表了要配置颜色信息；&lt;&#x2F;li&gt;
&lt;li&gt;对于新加的&lt;code&gt;VertexAttribute&lt;&#x2F;code&gt;，其&lt;code&gt;offset&lt;&#x2F;code&gt;字段填入的值是偏移过position字节数据长度；&lt;&#x2F;li&gt;
&lt;li&gt;将颜色信息数据指定为着色器中的location为1的地方。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;接下来，我们只需要修改着色器代码&lt;code&gt;VertexInput&lt;&#x2F;code&gt;结构体，增加一个color字段，&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;250.png&quot; alt=&quot;250&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;此时，渲染管线在构造这个&lt;code&gt;VertexInput&lt;&#x2F;code&gt;实例的时候，就能知道除了原有position字段数据外，还会把显存中的一份顶点数据的后面float32x3的大小数据映射到&lt;code&gt;@location(1) color: vec3f&lt;&#x2F;code&gt;上了。&lt;&#x2F;p&gt;
&lt;p&gt;当然，仅仅给&lt;code&gt;VertexInput&lt;&#x2F;code&gt;增加color字段，对于我们最终的渲染效果目前来说是没有任何影响的，因为我们压根儿没有消费这个color字段。为了消费这个字段，并让最终渲染的三角形的颜色产生变化，接下来就让我们关注一下着色器代码，看看还需要做什么。&lt;&#x2F;p&gt;
&lt;p&gt;首先，我们之前讲到过，对于顶点着色器的方法，我们返回的是&lt;code&gt;@builtin(position) vec4&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;，这意味着每次顶点着色器运行以后，会得到一个顶点的位置数据。在本例中，在所有顶点都执行以后，我们会得到3个顶点位置，渲染管线会拿着这3个位置构建一个三角形，并进行栅格化，再调用片元着色器，然后我们会再片元着色器中为每一个像素指定颜色。那么这里有一个问题：我们只能够在&lt;strong&gt;顶点&lt;&#x2F;strong&gt;着色器中返回每个顶点的位置吗？答案当然是否定的。除了直接返回一个&lt;code&gt;@builtin(position)&lt;&#x2F;code&gt;修饰的数据类型，我们还可以返回一个结构体，只要这个结构体中有一个字段用&lt;code&gt;@bultiin(position)&lt;&#x2F;code&gt;修饰即可：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;260.png&quot; alt=&quot;260&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这段修改后的代码的效果其实和之前是一样的，只不过我们用过了结构体来包裹。在返回结构体的形式下，我们可以在结构体中加入一些其他的字段，并且，在片元着色器节点还可以访问到顶点着色器输入结构体数据：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;270.png&quot; alt=&quot;270&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上述的着色器代码编写完成以后，理论上运行程序，你会发现如下的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;280.png&quot; alt=&quot;280&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;wow，一个&lt;strong&gt;渐变&lt;&#x2F;strong&gt;的三角形！然而，这就结束了吗？非也！&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ru-he-de-dao-yan-se&quot;&gt;如何得到颜色&lt;&#x2F;h2&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️笔者水平有限，因此后面的内容笔者仅能靠自己目前浅显的理解进行总结，其中可能会存在一些不到位或不正确的理解，这里恳请相关专业人士对错误的内容批评指出。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;如果读者认真看到现在，并且仔细思考了以后，我相信你会有一些疑问。首先，目前我们只有3个顶点，那么顶点着色器理论上来讲只会被调用3次，也就是说，我们总共只会得到3个&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;数据并返回给渲染管线，且这3个&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;实例的color字段分别只会是&lt;code&gt;(1.0, 0.0, 0.0)&lt;&#x2F;code&gt;（红色）、&lt;code&gt;(0.0, 1.0, 0.0)&lt;&#x2F;code&gt;（绿色）以及 &lt;code&gt;(0.0, 0.0, 1.0)&lt;&#x2F;code&gt;（蓝色）。然而，我们最终渲染的三角形是一个&lt;strong&gt;渐变&lt;&#x2F;strong&gt;三角形。根据片元着色器的作用，它会在每一个像素处理阶段被调用，这是否能够表明一件事：片元着色器代码中的消费的&lt;code&gt;color&lt;&#x2F;code&gt;字段，和前面的&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;的&lt;code&gt;color&lt;&#x2F;code&gt;其实不是一个东西？答案确实如此。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;290.png&quot; alt=&quot;290&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;细心的读者会发现，在&lt;code&gt;fs_main&lt;&#x2F;code&gt;入参，尽管类型是&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;，但我刻意的避免了使用&lt;code&gt;vertex_output&lt;&#x2F;code&gt;作为名称，而是使用了&lt;code&gt;data&lt;&#x2F;code&gt;，其实就在暗示从顶点着色器&lt;code&gt;vs_main&lt;&#x2F;code&gt;返回的&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;跟这里片元着色器&lt;code&gt;fs_main&lt;&#x2F;code&gt;得到的输入&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;实例并不是一个东西。让我们开拓下思维，结构体的本质是什么？&lt;strong&gt;实际上，结构体只是一种对内存数据的具名表达而已&lt;&#x2F;strong&gt;，在这里我们仅仅通过了&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;这个具名的描述内存数据形态的标识作为了顶点着色器和片元着色器的桥梁而已。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;300.png&quot; alt=&quot;300&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;换句话说，如果我们改成下面的代码，我们的程序同样能够正确的运行：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;310.png&quot; alt=&quot;310&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;那顶点着色器输出的位置和颜色信息，最终是如何影响到片元着色器的输入的呢？对于渲染管线来说，在顶点着色器执行以后，它会得到三个顶点数据，而其中就有通过&lt;code&gt;@builtin(position)&lt;&#x2F;code&gt;标识的，能够表达位置信息的数据。很显然，有了三个点的位置信息，在图元装配结合光栅化以后，我们能够得到一个最终图形的上的任意一个像素点位置信息：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;320.png&quot; alt=&quot;320&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;每一个顶点中我们都增加了一份数据用来表示颜色（color字段）。那么，对于三角面中任意一个像素的点位置的color字段数据，实际上是三个顶点颜色数据在&lt;strong&gt;此位置&lt;&#x2F;strong&gt;上的算法叠加：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;330.png&quot; alt=&quot;330&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;即，我们可以用一个方法来表达三角面上任意一个点的颜色：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;color&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v1_pos&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v1_color&lt;&#x2F;span&gt;&lt;span&gt;), (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v2_pos&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v2_color&lt;&#x2F;span&gt;&lt;span&gt;), (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v3_pos&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v3_color&lt;&#x2F;span&gt;&lt;span&gt;), any_pos) -&amp;gt; color
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;通过输入三个顶点的位置和颜色，以及任何一个三角面上的点位置，就能算出该点的颜色数据。但值得注意的是，在我们的场景中，我们对&lt;code&gt;@location(0)&lt;&#x2F;code&gt;位置的数据取名为了color，表明用该字段作为颜色字段，但是在内存中，管线只知道这里有一份类型为&lt;code&gt;vec3f&lt;&#x2F;code&gt;的数据罢了。所以，对应的更加通用的公式应该是：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;get_data&lt;&#x2F;span&gt;&lt;span&gt;((&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v1_pos&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v1_data&lt;&#x2F;span&gt;&lt;span&gt;), (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v2_pos&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v2_data&lt;&#x2F;span&gt;&lt;span&gt;), (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v3_pos&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;v3_data&lt;&#x2F;span&gt;&lt;span&gt;), any_input_pos) -&amp;gt; data_in_pos
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;那么关于这个的具体实现在本文中不再细讲，但最简单的方式应该就是线性叠加，读者可以自行深入这块的内容。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-location&quot;&gt;关于@location&lt;&#x2F;h2&gt;
&lt;p&gt;另外我们还需要着色器代码解释另一个东西。仔细观察代码，无论是&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;还是&lt;code&gt;FragmentInput&lt;&#x2F;code&gt;结构体，我们都在&lt;code&gt;color&lt;&#x2F;code&gt;这个字段使用了注解&lt;code&gt;@location(0)&lt;&#x2F;code&gt;。在前面的&lt;code&gt;VertexInput&lt;&#x2F;code&gt;结构体的中的&lt;code&gt;color&lt;&#x2F;code&gt;字段我们使用了同样的注解，其含义是我们把一份内存中对应位置的数据设置为了着色器中一个结构体中&lt;code&gt;location = 0&lt;&#x2F;code&gt;的位置的字段。那么这里是不是也是同样的意义呢？答案确实是如此的。&lt;&#x2F;p&gt;
&lt;p&gt;读者可以这样理解，在光栅化后，每一次片元着色器的输入也是一份内存数据，这份内存数据我们同样可以使用一个结构体来访问（因为结构体是内存中的数据的可读性表达），但是结构体中的字段可以有很多个，每个字段究竟是内存中哪一块的数据，需要有一个明确的指明：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;340.png&quot; alt=&quot;340&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在上图中，无论是&lt;code&gt;VertexOutput&lt;&#x2F;code&gt;结构体还是&lt;code&gt;FragmentInput&lt;&#x2F;code&gt;结构体，其内存的布局是一致的，因此在片元着色器执行的时候，渲染管线提供的数据我们用上述两种结构体够可以表达对应的内存数据。再想的远一点，这个&lt;code&gt;location&lt;&#x2F;code&gt;只能是0吗？其实也不是，因为本质上讲，它是一段数据的标识，只是本例中，我们使用了0这个位置标识而已，如果你乐意，你还可以编写如下标识：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;350.png&quot; alt=&quot;350&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;甚至，你还可以不编写任何的结构体作为输入，而是直接使用&lt;code&gt;@location&lt;&#x2F;code&gt;来定位：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-18&#x2F;360.png&quot; alt=&quot;360&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于最后的一种使用方式，请读者自己揣摩一下～&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本文的内容较多引申了不少额外的内容，读者可以慢慢阅读消化，希望能够对认识wgpu以及图形学工程有更进一步的理解和认识。在接下来的内容，我们将会认识wgpu中有关于以及图形学工程相关的更多的内容，敬请期待！&lt;&#x2F;p&gt;
&lt;p&gt;本章的代码仓库在这里：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;tree&#x2F;main&#x2F;ch03_vertex_buffer&quot;&gt;ch03_vertex_buffer&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;后续文章的相关代码也会在该仓库中添加，所以感兴趣的读者可以点个star，谢谢你们的支持！&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Wgpu图文详解（02）渲染管线与着色器</title>
        <published>2024-11-07T00:00:00+00:00</published>
        <updated>2024-11-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Wgpu图文详解（02）渲染管线与着色器/"/>
        <id>https://zhen.wang/article/Wgpu图文详解（02）渲染管线与着色器/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Wgpu图文详解（02）渲染管线与着色器/">&lt;p&gt;在本系列的第一篇文章中（《Wgpu图文详解（01）窗口与基本渲染》），我们介绍了如何基于0.30+版本的winit搭建Wgpu的桌面环境，同时也讲解了关于Wgpu一些基本的概念、模块以及架构思路，并基于wgpu库实现了一个能展示有颜色背景的窗体。而在本篇文章中，我们将开始介绍Wgpu中的渲染管线以及着色器，并通过这两个基本要素，在原有窗口的基础上，渲染一个三角形。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;⚠️这章的内容很多，相比上一章来说，需要读者具备更多关于图形学的理论知识，否则看起来还是会一头雾水，不过笔者尽可能的将一些内容讲的细一点，特别是着色器代码与代码中的某些配置的关系，旨在让读者能够不那么“头晕”。当然，作者的能力有限，所以针对图形学的内容，读者可以自行了解熟悉后，后再看本文，当然这里也毛遂自荐下自己的另一篇文章《关于计算机图形学的一些介绍（01）基本要素与空间变换》（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zhuanlan.zhihu.com&#x2F;p&#x2F;711896993&quot;&gt;知乎&lt;&#x2F;a&gt;、&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zhen.wang&#x2F;article&#x2F;%E5%85%B3%E4%BA%8E%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BB%8B%E7%BB%8D%EF%BC%8801%EF%BC%89%E5%9F%BA%E6%9C%AC%E8%A6%81%E7%B4%A0%E4%B8%8E%E7%A9%BA%E9%97%B4%E5%8F%98%E6%8D%A2&#x2F;&quot;&gt;博客&lt;&#x2F;a&gt;）&lt;&#x2F;p&gt;
&lt;p&gt;⚠️本章节开始的wgpu使用版本为&lt;strong&gt;23.0.0&lt;&#x2F;strong&gt;，为2024年的最后一个major升级：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gfx-rs&#x2F;wgpu&#x2F;releases&#x2F;tag&#x2F;v23.0.0&quot;&gt;release&#x2F;tab&#x2F;v23.0.0&lt;&#x2F;a&gt;，该版本有break change，请读者确保版本一致。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;ji-ben-gai-nian-dao-ru&quot;&gt;基本概念导入&lt;&#x2F;h1&gt;
&lt;p&gt;首先，让我们先简单的介绍一下什么是&lt;strong&gt;管线Pipeline&lt;&#x2F;strong&gt;。从实际应用的角度看，管线类似于工厂内的生产线：从一端开始，接收基础原料，随后，生产线上各工序节点依次对这些原料进行加工处理，逐步形成最终产品。同样，计算机图形学工程中的管线的形式也十分类似，我们把一些有关最终要渲染的图像的必要数据作为输入，通过管线的层层作业，最终得到能够渲染到屏幕设备上的图形、颜色。此外，管线还有一个比较有价值的作用就是能够将处理数据的分工变的更加明确，同时，每一个步骤也能具备独立配置、编程的能力。&lt;&#x2F;p&gt;
&lt;p&gt;当然，在图形学工程中的管线是有很多种类的，比如渲染管线RenderPipeline、计算管线ComputePipeline。不同种类的管线负责了不同的工作，但其本质是一样的：流程化的处理图形数据。为了通过Wgpu渲染一个三角形，我们至少需要构建一个&lt;strong&gt;渲染管线&lt;&#x2F;strong&gt;，来达到最终的目的。&lt;&#x2F;p&gt;
&lt;p&gt;在介绍渲染管线的同时，我们就不得不介绍另一个重要的东⻄：&lt;strong&gt;着色器Shader&lt;&#x2F;strong&gt;。正如上面所说的，渲染管线的本质是一条包含多个环节的作业流水线。为了让我们能够更加方便的通过程序来控制每一个作业环节，图形学工程引入了&lt;strong&gt;着色器&lt;&#x2F;strong&gt;这一概念。需要强调的是，着色器并不是某种类似上色的功能，而是一段可编程的处理程序，能够让我们在渲染管线中的某些环节通过程序去控制结果。所以，整体结合来看，我们可以将渲染管线与着色器的关系用一张图来表达：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;000.png&quot; alt=&quot;000&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;上面这张图只是一个概念上的简单的关系图。在实际的图形学工程中，远远要比这个复杂的多，不过为了让读者有一个感性的认知，可以暂时按照上图的关系来理解管线和着色器的关系。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;既然着色器本质是一段程序，那我们不可避免的需要编写这样的程序。在Wgpu中，我们使用wgsl（Web GPU Shading Language）编写着色器程序。当然，就如同C&#x2F;C++、Rust等高级程序语言一样，我们编写的wgsl只是源代码，因此，我们还需要将这些源代码编译为着色器的二进制程序，这个过程几乎不用我们操心，因为Wgpu在运行过程中会去编译并调用这些着色器代码。&lt;&#x2F;p&gt;
&lt;p&gt;好了，到目前为止我们对管线与着色器有了一个大体的认识，当然，光有理论知识是不够了，接下来我们就开始从代码工程出发，编写构建渲染管线的相关代码以及着色器程序。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;zhun-bei-jie-duan&quot;&gt;准备阶段&lt;&#x2F;h1&gt;
&lt;blockquote&gt;
&lt;p&gt;本章的代码工程项目将会在第一篇文章搭建结果的基础上进行修改。因此在继续后面的讲解前，请确保你已经充分理解了第一章的内容并搭建好了环境。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;首先，让我们在&lt;code&gt;WgpuCtx&lt;&#x2F;code&gt;这个结构中添加一个新的字段&lt;code&gt;render_pipeline&lt;&#x2F;code&gt;，其类型为&lt;code&gt;wgpu::RenderPipeline&lt;&#x2F;code&gt;。接着，让我们准备一个结构无关的方法，其签名为：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;create_pipeline&lt;&#x2F;span&gt;&lt;span&gt;() -&amp;gt; wgpu::RenderPipeline;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;最后，让我们在WgpuCtx的new_async方法中的指定位置调用上述的&lt;code&gt;create_pipeline&lt;&#x2F;code&gt;方法，并将得到的RenderPipeline交给WgpuCtx存放。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;005.png&quot; alt=&quot;005&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;接下来，让我们编写一段着色器程序。在项目目录下创建一个名为&lt;code&gt;shader.wgsl&lt;&#x2F;code&gt;的文件，并在其中添加如下wgsl代码：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;@vertex
&lt;&#x2F;span&gt;&lt;span&gt;fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -&amp;gt; @builtin(position) vec4&amp;lt;f32&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    let x = f32(i32(in_vertex_index) - 1);
&lt;&#x2F;span&gt;&lt;span&gt;    let y = f32(i32(in_vertex_index &amp;amp; 1u) * 2 - 1);
&lt;&#x2F;span&gt;&lt;span&gt;    return vec4&amp;lt;f32&amp;gt;(x, y, 0.0, 1.0);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;@fragment
&lt;&#x2F;span&gt;&lt;span&gt;fn fs_main() -&amp;gt; @location(0) vec4&amp;lt;f32&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    return vec4&amp;lt;f32&amp;gt;(1.0, 0.0, 0.0, 1.0);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;至于这段wgsl代码的含义我们先不着急说明，后面我们会详细的解释，此时就简单理解为我们编写了一份着色器程序源代码，并让其在渲染管线中发挥作用。&lt;&#x2F;p&gt;
&lt;p&gt;接下来让我们修改一下 create_pipeline 的方法签名，增加两个入参：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;create_pipeline&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;device&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;wgpu::Device, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;--- 参数1
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;swap_chain_format&lt;&#x2F;span&gt;&lt;span&gt;: wgpu::TextureFormat, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 参数2
&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; wgpu::RenderPipeline { 
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;... 
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;对于第一个参数&lt;code&gt;wgpu::Device&lt;&#x2F;code&gt;，看过第一章的读者应该知道，这个实例是通过adapter调用&lt;code&gt;request_device&lt;&#x2F;code&gt;得到的，是对逻辑设备的抽象的实例：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于第二个参数，&lt;code&gt;wgpu::TextureFormat&lt;&#x2F;code&gt;，则来源于完成配置后的surface_config的format字段。所以，在调用点我们需要做出适当的修改：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在准备工作完成以后，我们的项目现在大概长这样：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;至此，我们已经准备好了一个创建管线的环境了。接下来就让我们开始关注于&lt;code&gt;create_pipeline&lt;&#x2F;code&gt;这个方法的具体实现，开始真正的创建渲染管线、着色器以及理解它们。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chuang-jian-xuan-ran-guan-xian&quot;&gt;创建渲染管线&lt;&#x2F;h1&gt;
&lt;p&gt;对于&lt;code&gt;create_pipeline&lt;&#x2F;code&gt;的方法体，我们填入如下的内容：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;通过代码注释，我们可以了解到创建一个基础的渲染管线至少有以下两步：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;通过&lt;code&gt;wgpu::Device&lt;&#x2F;code&gt;提供的API&lt;code&gt;create_shader_module&lt;&#x2F;code&gt;加载着色器程序模块；&lt;&#x2F;li&gt;
&lt;li&gt;通过&lt;code&gt;wgpu::Device&lt;&#x2F;code&gt;提供的API&lt;code&gt;create_render_pipeline&lt;&#x2F;code&gt;，结合步骤1中得到的着色器模块实例创建渲染管线。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;对于第一步来说，读者可以直接参考上述代码即可，其含义理解起来并不困难，核心就是从加载着色器源代码内容，并通过一系列构造过程得到一个ShaderModule（着色器程序模块）。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;wgpu的很多结构体都会有一个名为&lt;code&gt;label&lt;&#x2F;code&gt;的字段，这个字段对于运行时没有什么影响，仅仅是作为Debug调试阶段时方便定位数据的。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;对于第二步调用&lt;code&gt;create_render_pipeline&lt;&#x2F;code&gt;，其具体的内容如下所示：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;笔者在上图代码中将其标记为了5个部分的配置。其中，第1个和第5个配置本章暂不涉及，按上图示例代码传入相关默认值即可，这些参数我们会在后续的文章中逐步讲解，本文咱不赘述。让我们重点关注上图中的第2、3、4个部分。&lt;&#x2F;p&gt;
&lt;p&gt;⚠️接下来的内容，除了有关wgpu本身使用内容以外，还会涉及到计算机图形学中的一些重要概念。什么是顶点vertex，什么是片元fragment，什么是图元primitive，这些都是学习计算机图形学必不可少的知识点。&lt;strong&gt;由于本系列文章重点是从工程的角度介绍如何使用wgpu，所以关于图形学的知识点不会特别介绍，需要读者自行学习，本文假设读者已经具备了相关的知识&lt;&#x2F;strong&gt;。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;再次自荐《关于计算机图形学的一些介绍（01）基本要素与空间变换》（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zhen.wang&#x2F;article&#x2F;%E5%85%B3%E4%BA%8E%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BB%8B%E7%BB%8D%EF%BC%8801%EF%BC%89%E5%9F%BA%E6%9C%AC%E8%A6%81%E7%B4%A0%E4%B8%8E%E7%A9%BA%E9%97%B4%E5%8F%98%E6%8D%A2&#x2F;&quot;&gt;博客地址&lt;&#x2F;a&gt;，&lt;a href=&quot;https:&#x2F;&#x2F;zhen.wang&#x2F;article&#x2F;Wgpu%E5%9B%BE%E6%96%87%E8%AF%A6%E8%A7%A3%EF%BC%8802%EF%BC%89%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF%E4%B8%8E%E7%9D%80%E8%89%B2%E5%99%A8&#x2F;%E5%85%B3%E4%BA%8E%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9B%BE%E5%BD%A2%E5%AD%A6%E7%9A%84%E4%B8%80%E4%BA%9B%E4%BB%8B%E7%BB%8D%EF%BC%8801%EF%BC%89%E5%9F%BA%E6%9C%AC%E8%A6%81%E7%B4%A0%E4%B8%8E%E7%A9%BA%E9%97%B4%E5%8F%98%E6%8D%A2&quot;&gt;知乎地址&lt;&#x2F;a&gt;）。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;ding-dian-zhao-se-qi&quot;&gt;顶点着色器&lt;&#x2F;h2&gt;
&lt;p&gt;让我们先聚焦第一个部分：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;vertex: wgpu::VertexState {
&lt;&#x2F;span&gt;&lt;span&gt;    module: &amp;amp;shader,
&lt;&#x2F;span&gt;&lt;span&gt;    entry_point: Some(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;vs_main&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;),
&lt;&#x2F;span&gt;&lt;span&gt;    buffers: &amp;amp;[],
&lt;&#x2F;span&gt;&lt;span&gt;    compilation_options: Default::default(),
&lt;&#x2F;span&gt;&lt;span&gt;},
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;第一个参数&lt;code&gt;module&lt;&#x2F;code&gt;，表明了我们需要从哪个ShaderModule实例来获取&lt;strong&gt;顶点着色器&lt;&#x2F;strong&gt;程序。在前面，我们曾编写了一份着色器代码，并通过&lt;code&gt;create_shader_module&lt;&#x2F;code&gt;创建了一个ShaderModule实例，这里作为该参数的值传入即可。&lt;&#x2F;p&gt;
&lt;p&gt;第二个参数&lt;code&gt;entry_point&lt;&#x2F;code&gt;，表明了&lt;strong&gt;顶点着色器&lt;&#x2F;strong&gt;程序入口点，这个所谓的入口点类似于我们常规程序中的main函数一样。不过需要注意的是，这里我们填入的是&lt;code&gt;&quot;vs_main&quot;&lt;&#x2F;code&gt;，还记得我们之前编写的&lt;code&gt;shader.wgsl&lt;&#x2F;code&gt;代码吗？在其中有我们编写的这样一段代码：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在该段代码中，我们使用了一个注解&lt;code&gt;@vertex&lt;&#x2F;code&gt;来表明接下来的函数是一个顶点着色器有关的函数，然后，我们给这个方法命名为&lt;code&gt;&quot;vs_main&quot;&lt;&#x2F;code&gt;。相对应的，在上面的Rust代码中的&lt;code&gt;entry_point&lt;&#x2F;code&gt;字段我们对应填入的就是这个&lt;code&gt;vs_main&lt;&#x2F;code&gt;。所以，目前的情况如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;请注意，本文使用的wgpu版本为23.x +，该版本与22.x以及0.2x版本的一个重要break change：关于VertexState以及接下后续介绍的FragmentState的entry_point字段的类型由旧版本的&lt;code&gt;&amp;amp;&#x27;a str&lt;&#x2F;code&gt;该为了&lt;code&gt;Option&amp;lt;&amp;amp;&#x27;a str&amp;gt;&lt;&#x2F;code&gt;。因此本文都传的&lt;code&gt;Some(xxx)&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在了解了这样的配置关系以后，我们还需要知道这段顶点着色器代码的意义。首先，该方法会在&lt;strong&gt;每一次&lt;&#x2F;strong&gt;处理顶点的时候被调用。假设现在我们场景中提供了n个顶点，那么渲染管线在顶点处理这一环节的时候，会调用n次这个顶点着色器程序。对于这个&lt;code&gt;vs_main&lt;&#x2F;code&gt;方法的参数，首先是入参&lt;code&gt;@builtin(vertex_index) in_vertex_index: u32&lt;&#x2F;code&gt;，每次调用该&lt;code&gt;vs_main&lt;&#x2F;code&gt;方法的时候，会传入一个u32类型的值，该值是wgsl&lt;strong&gt;内建的顶点索引值&lt;&#x2F;strong&gt;（如果是n个顶点的话，通常是0到n-1）。可以把这段流程想象成如下伪代码：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;遍历：0 &amp;lt;= 顶点索引index n-1 {
&lt;&#x2F;span&gt;&lt;span&gt;	顶点处理结果 = 执行vs_main(顶点索引index)
&lt;&#x2F;span&gt;&lt;span&gt;	拿着顶点处理结果干其他事...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;同时，该方法每一次调用完成以后，会返回一个&lt;code&gt;vec&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;，同时用&lt;code&gt;@builtin(position)&lt;&#x2F;code&gt;，表明该方法返回的是一个&lt;strong&gt;内建的位置数据&lt;&#x2F;strong&gt;。可能读者对于这块还感觉到非常抽象。那让我们用一个更见实际的例子来解释。&lt;&#x2F;p&gt;
&lt;p&gt;假设现在有如下的一个三角形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于这个三角形的三个顶点，按照逆时针方向，其索引依次是0、1、2。在渲染管线的顶点着色器处理的环节，根据我们前面讲到的，每一个顶点都调用一次&lt;code&gt;vs_main&lt;&#x2F;code&gt;方法，那么结果如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;110.png&quot; alt=&quot;110&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;值得注意的是，在代码中求y值时，代码使用的是将数据与1进行&lt;strong&gt;二进制按位与&lt;&#x2F;strong&gt;操作，因此，当index = 2时，&lt;code&gt;2 &amp;amp; 1&lt;&#x2F;code&gt;实际上是&lt;code&gt;二进制10 &amp;amp; 二进制01&lt;&#x2F;code&gt;，按位与的结果就是&lt;code&gt;二进制00&lt;&#x2F;code&gt;，即就是0。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;对于每个顶点来说，我们求得了其位置坐标。但值得注意的是，返回的位置坐标是一个4维的，其中前两个分量分别对应x轴和y轴，同时也是我们根据顶点索引动态得到的；第三个分量是z轴，且均为&lt;code&gt;0.0&lt;&#x2F;code&gt;，表明所有的顶点都处在z轴等于0的平面上；最后一分量是w值，通常都是&lt;code&gt;1.0&lt;&#x2F;code&gt;（对于这个w分量，务必请读者自行仔细了解其数学意义，本文不做赘述）。&lt;&#x2F;p&gt;
&lt;p&gt;对上述结果整理一下，我们可以知道，三个顶点依次处理的结果就是生成了在同一个2维平面上（因为z均为0）的三个点，其坐标分别是：&lt;code&gt;（-1.0, -1.0）&lt;&#x2F;code&gt;、&lt;code&gt;(0.0, 1.0)&lt;&#x2F;code&gt;、&lt;code&gt;(1.0, -1.0)&lt;&#x2F;code&gt;。那么这些坐标在wgpu下的意义是什么呢？这里我们直接给一个结论。首先我们知道wgpu渲染的时候，对应物理屏幕上是存在一个视口viewport的（如果忘记了，请在阅读下本系列的第一章内容），对于这个视口来说，无论其宽高的绝对大小值是多少，总是以中心位原点，视口上下y范围为1.0到-1.0，以及视口左右x的范围为-1.0到1.0的坐标区域：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;120.png&quot; alt=&quot;120&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;因此，上述坐标的结果就是我们能够渲染一个如下的顶点刚好顶满视口的三角形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;130.png&quot; alt=&quot;130&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;目前的代码进度还无法渲染出上图的结果，这里只是为了让读者更加直观了解坐标与最终渲染的关系&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;当然，如果我们适当的修改顶点着色器中代码，将x、y值分别再乘以0.5，就能看到一个缩小版的三角形了：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;140.png&quot; alt=&quot;140&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;现在我们已经讲解了关于Vertex配置&lt;strong&gt;VertexState&lt;&#x2F;strong&gt;的&lt;strong&gt;module&lt;&#x2F;strong&gt;和&lt;strong&gt;entry_point&lt;&#x2F;strong&gt;字段了，对于剩余的&lt;strong&gt;buffers&lt;&#x2F;strong&gt;和&lt;strong&gt;compilation_options&lt;&#x2F;strong&gt;字段来说，本章暂时不进行讨论，只需要默认即可：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;vertex: wgpu::VertexState {
&lt;&#x2F;span&gt;&lt;span&gt;    module: &amp;amp;shader,
&lt;&#x2F;span&gt;&lt;span&gt;    entry_point: Some(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;vs_main&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;),
&lt;&#x2F;span&gt;&lt;span&gt;    buffers: &amp;amp;[], &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;--- 默认
&lt;&#x2F;span&gt;&lt;span&gt;    compilation_options: Default::default(), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;--- 默认
&lt;&#x2F;span&gt;&lt;span&gt;},
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;tu-yuan-pei-zhi&quot;&gt;图元配置&lt;&#x2F;h2&gt;
&lt;p&gt;对于图片的配置如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span&gt;primitive: wgpu::PrimitiveState {
&lt;&#x2F;span&gt;&lt;span&gt;    topology: wgpu::PrimitiveTopology::TriangleList,
&lt;&#x2F;span&gt;&lt;span&gt;    ..Default::default()
&lt;&#x2F;span&gt;&lt;span&gt;},
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在本文中笔者仅展示一个核心的字段配置：&lt;code&gt;topology&lt;&#x2F;code&gt;。对于这个参数，有以下几个目前能够支持的配置：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PointList：顶点数据是一系列点。每个顶点都是一个新点。也就是说，像上面的我们提供的3个顶点，最终并不会渲染为一个三角形，而是三个独立的点。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;LineList：顶点数据是一系列线条。每对顶点组成一条新线。顶点0 1 2 3创建两条线0 1和2 3。注意，这个枚举值配置下，我们提供的顶点必须要能够&lt;strong&gt;成对&lt;&#x2F;strong&gt;出现，像上面我们的3个顶点，最终只会渲染一条线，因为0、1构成一条线，而顶点2没办法构成另一条线了。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;LineStrip：顶点数据是一条直线。每组两个相邻的顶点形成一条线。顶点0 1 2 3创建三条线0 1、1 2和2 3。也就是说上面的例子最终不会渲染一个填充了内容的三角形，而是只有边线的三角形。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;TriangleList（默认）：顶点数据是一系列三角形。每组3个顶点组成一个新的三角形。顶点0 1 2 3 4 5创建两个三角形0 1 2和3 4 5。这是我们的默认配置。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;TriangleStrip：顶点数据是一个三角形条带。每组三个相邻顶点形成一个三角形。顶点0 1 2 3 4 5创建四个三角形0 1 2、2 1 3、2 3 4和4 3 5。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;blockquote&gt;
&lt;p&gt;通过解释，相信读者应该能够理解上述配置的结果，当然读者会在后续的文章中用更多的例子来讲解这块的内容。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;pian-yuan-zhao-se-qi&quot;&gt;片元着色器&lt;&#x2F;h2&gt;
&lt;p&gt;接下来让我们关注片元着色器的部分。要了解片元着色器，我们首先要知道什么是片元，片元是怎么来的。在前面的的顶点着色器部分我们知道输入三个顶点索引，能够通过顶点着色器来计算出三个顶点的坐标，再通过图元的拓扑配置，来表明这三个点构成的是一个三角面（而不是三个点或者三条直线），再通过顶点坐标进而控制一个三角面在空间中的位置大小。有了位置大小以后，渲染管线的处理过程中会进行一个步骤：光栅化。光栅化逻辑就是对于几何图形上每一个“点”，在屏幕设备上找到对应的像素点的过程。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;150.png&quot; alt=&quot;150&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于光栅化的具体实现实现，就不在本文的讨论范围内了，对于这块感兴趣的同学可以自行查阅相关资料进行深入研究。&lt;&#x2F;p&gt;
&lt;p&gt;简单了解完光栅化基本形式和结果后，让我们回到本节的核心：片元fragment。片元实际上就是一个图形经过光栅化处理后的&lt;strong&gt;一个或多个像素的样本&lt;&#x2F;strong&gt;。在这有两点值得注意：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;尽管叫做&lt;strong&gt;片元&lt;&#x2F;strong&gt;，但通常指的是一个或少许多个像素大小的单位。也就是说，一个几何图形，经过光栅化会被分解为多个片元。&lt;&#x2F;li&gt;
&lt;li&gt;光栅化后得到的片元只是&lt;strong&gt;接近&lt;&#x2F;strong&gt;像素点，但并不完全等于像素点。片元是与像素相关的、待处理的数据集合，包括颜色、深度、纹理坐标等信息（深度和纹理坐标等先简单理解为一些额外数据）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;片元并非像素点，它只是接近像素点，所以通常来说，我们还会有一个步骤来对片元进行进一步的处理，好让它最终转换为屏幕上的像素点来进行呈现（此时基本就是带有rgba颜色的点了）。那么这个步骤实际上就是调用片元着色器进行处理。这个过程则是，渲染管线在顶点着色器处理后的某个步骤中，计算得到m个片元；随后，渲染管线会调用片元着色器，并把片元上下文内容通过参数传入到片元着色器的入口方法中，并返回对应片元在屏幕上的色彩：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;160.png&quot; alt=&quot;160&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;因此，我们先前在shader.wgsl中编写的&lt;strong&gt;片元着色器的代码&lt;&#x2F;strong&gt;其实就很容易理解了：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;170.png&quot; alt=&quot;170&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上述的代码中，首先我们使用&lt;code&gt;@fragment&lt;&#x2F;code&gt;注解标记了这个名为&lt;code&gt;fs_main&lt;&#x2F;code&gt;的方法为片元着色器的入口方法；其次，对于这个方法的实现，非常见简单，我们总是返回rgba为&lt;code&gt;(1.0, 0.0, 0.0, 1.0)&lt;&#x2F;code&gt;的红色颜色值。同时，配置方式如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;180.png&quot; alt=&quot;180&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;这里我们需要关注一个点。在片元着色器中，我们最终返回的类型定义是：&lt;code&gt;@location(0) vec4&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;，这个&lt;code&gt;vec4&amp;lt;f32&amp;gt;&lt;&#x2F;code&gt;读者应该理解，就是一个表示rgba的颜色值。那这个&lt;code&gt;@location(0)&lt;&#x2F;code&gt;什么含义呢？其实上图的配置过程能够给到一定的提示。在配置fragment参数时候，我们配置了&lt;code&gt;targets: &amp;amp;[Some(swap_chain_format.into())]&lt;&#x2F;code&gt;，这个targets是一个数组，我们传入了唯一一个元素&lt;code&gt;Some(swap_chain_format.into())&lt;&#x2F;code&gt;，而片元着色器的返回中配置的&lt;code&gt;@location(0)&lt;&#x2F;code&gt;，其意义就是把片元着色器计算得到的颜色“放到”位置索引为0的&lt;strong&gt;颜色目标&lt;&#x2F;strong&gt;，而这个&lt;strong&gt;颜色目标&lt;&#x2F;strong&gt;在这里就是由&lt;code&gt;swap_chain_format.into()&lt;&#x2F;code&gt;转换得到的颜色目标&lt;code&gt;ColorTargetState&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;190.png&quot; alt=&quot;190&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;至此，我们大体上了解了片元着色器中的基本使用方式。不过在本例中，我们的片元着色器并没有任何的入参，且始终返回的是一个固定的颜色值。不过在后续文章，我们会通过更多的示例来讲解片元着色器。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;shi-yong-xuan-ran-guan-xian&quot;&gt;使用渲染管线&lt;&#x2F;h1&gt;
&lt;p&gt;上面的代码中，我们仅仅是在构造Wgpu上下文的阶段创建了一个渲染管线并将它存放到了WgpuCtx的render_pipeline字段。那么我们应该在哪里去使用这个渲染管线呢？答案就是在之前我们编写的WgpuCtx的draw方法中去使用它：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;200.png&quot; alt=&quot;200&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于新增的代码，第一步中&lt;code&gt;set_pipeline(xxx)&lt;&#x2F;code&gt;很好理解这里不再赘述；第二步对于调用渲染通道（render_pass）的draw方法的参数需要说明一下。该draw方法的第一个参数定义是：&lt;code&gt;vertices: Range&amp;lt;u32&amp;gt;&lt;&#x2F;code&gt;，在这里我们传入了一个&lt;code&gt;0..3&lt;&#x2F;code&gt;，其意义就是告诉渲染管线，我提供了0、1、2三个顶点。再回看我们的顶点着色器代码，我们在顶点着色器的入口方法定义的参数：&lt;code&gt;@builtin(vertex_index) in_vertex_index: u32&lt;&#x2F;code&gt;，这里的&lt;code&gt;@builtin(vertex_index)&lt;&#x2F;code&gt;就是想表达这样一个事实：在顶点着色器代码入口给我依次传入0、1、2的顶点索引，好让我们能够通过一些计算得到我期望的三角形的三个几何顶点的位置。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;210.png&quot; alt=&quot;210&quot; &#x2F;&gt;对于第2个参数&lt;code&gt;instances: Range&amp;lt;u32&amp;gt;&lt;&#x2F;code&gt;在本章中情况下我们都传&lt;code&gt;0..1&lt;&#x2F;code&gt;，即只有一个渲染实例。当然，当你需要绘制多个相同或相似的对象时，可以使用实例化渲染。&lt;code&gt;instances&lt;&#x2F;code&gt; 参数指定了要绘制的实例数量。同时，我们还可以在顶点着色器中通过&lt;code&gt;@builtin(instance_index)&lt;&#x2F;code&gt;来得到当前的实例索引。举个例子，假设现在我们想要绘制两个三角形。一种方式是提供两个三角形的顶点（比如，我们传入0-5共计6个顶点)来表示2个三角形，我们也可以像之前一样，传入3个顶点索引，但构造两个实例：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;220.png&quot; alt=&quot;220&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;然后，我们修改原先的顶点着色器入口参数，添加对实例索引的访问：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;230.png&quot; alt=&quot;230&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;再次运行程序，我们会发现现在窗口中渲染了两个三角形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-11-07&#x2F;240.png&quot; alt=&quot;240&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;至此本章的内容就基本上接近尾声了，在本文中我们在第一章的基础上，进一步介绍了渲染管线以及着色器代码，并通过代码实践，希望让读者更加清晰了解整个过程。当然，目前为止我们仅仅在顶点着色器处理阶段消费了顶点的索引，以及在片元着色器处理阶段返回了固定的颜色值，而实际应用场景下远没有如此简单。因此在接下来的文章我们将介绍新的概念来实现如何更加动态的构建三角形。&lt;&#x2F;p&gt;
&lt;p&gt;本章的代码仓库在这里：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;tree&#x2F;main&#x2F;ch02_render_a_triangle&quot;&gt;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;wgpu_winit_example&#x2F;tree&#x2F;main&#x2F;ch02_render_a_triangle&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;后续文章的相关代码也会在该仓库中添加，所以感兴趣的读者可以点个star，谢谢你们的支持！&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>【Bevy实战】2D场景下Camera实践</title>
        <published>2024-09-26T00:00:00+00:00</published>
        <updated>2024-09-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/【Bevy实战】2D场景下Camera实践/"/>
        <id>https://zhen.wang/article/【Bevy实战】2D场景下Camera实践/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/【Bevy实战】2D场景下Camera实践/">&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&quot;&gt;Bevy&lt;&#x2F;a&gt;，一个用Rust构建的令人耳目一新的简单数据驱动游戏引擎。如果你是一名Rust开发者，同时又对游戏开发比较感兴趣，那么Bevy一定是你会接触甚至是使用的游戏引擎。当然，本文关注的重点并不是来介绍Bevy，以及它的一些基本概念，关于这块的内容读者完全可以到Bevy的官网、Github主页进行学习；同时，本文也不是一篇介绍Rust编程细节的文章，因此，本文面向的对象是有Rust开发经验（至少是要入门）的小伙伴。接下来就让我们开始本文的主要内容：Bevy在2D场景下的Camera实践。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;ji-ben-gai-nian&quot;&gt;基本概念&lt;&#x2F;h1&gt;
&lt;p&gt;Camera摄影机驱动Bevy中的所有渲染。他们负责配置要绘制的内容、如何绘制以及在哪里绘制。为了更形象的讲解，我们先编写如下代码：&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;PS：假设读者的其他环境、依赖已经配置Ok&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;use &lt;&#x2F;span&gt;&lt;span&gt;bevy::prelude::*;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;    App::new()
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add_plugins&lt;&#x2F;span&gt;&lt;span&gt;(DefaultPlugins)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;add_systems&lt;&#x2F;span&gt;&lt;span&gt;(Startup, startup)
&lt;&#x2F;span&gt;&lt;span&gt;        .&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;run&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;startup&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;commands&lt;&#x2F;span&gt;&lt;span&gt;: Commands, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;asset_server&lt;&#x2F;span&gt;&lt;span&gt;: Res&amp;lt;AssetServer&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; icon: Handle&amp;lt;Image&amp;gt; = asset_server.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;img1.png&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(SpriteBundle {
&lt;&#x2F;span&gt;&lt;span&gt;        texture: icon.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上述的代码核心过程：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;加载Bevy默认的插件&lt;&#x2F;li&gt;
&lt;li&gt;启动阶段（Startup）生成一张精灵图（来自img1.png）&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;此时我们运行项目，会发现仅有一个没有任何内容的，黑色背景的窗口：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;010.png&quot; alt=&quot;010&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;造成这个现象的原因是：在默认的情况下，Bevy“游戏世界“中不存在摄像机Camera实体，因此无法将可渲染的内容”拍摄“下来并投射到屏幕上。为了解决这个问题也非常简单，在世界中增加一个摄像机实体即可：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;diff&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-diff &quot;&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;&lt;span&gt;fn startup(mut commands: Commands, asset_server: Res&amp;lt;AssetServer&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+   &#x2F;&#x2F; add camera.
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;+   commands.spawn(Camera2dBundle::default());
&lt;&#x2F;span&gt;&lt;span&gt;    &#x2F;&#x2F; then spawn Sprite.
&lt;&#x2F;span&gt;&lt;span&gt;    let img1: Handle&amp;lt;Image&amp;gt; = asset_server.load(&amp;quot;img1.png&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    commands.spawn(SpriteBundle {
&lt;&#x2F;span&gt;&lt;span&gt;        texture: img1.clone(),
&lt;&#x2F;span&gt;&lt;span&gt;        ..default()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上述代码我们添加了一个2D摄像机的实体，此时再次运行程序可以看到如下的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;020.png&quot; alt=&quot;020&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;你可以将上面的2D渲染想象成如下的样子：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;030.png&quot; alt=&quot;030&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;此时，让我们在项目工程中再增加一张图片，并且在之前生成实体的位置再生成一个新的Sprite：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;startup&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;commands&lt;&#x2F;span&gt;&lt;span&gt;: Commands, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;asset_server&lt;&#x2F;span&gt;&lt;span&gt;: Res&amp;lt;AssetServer&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add camera.
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(Camera2dBundle::default());
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; then spawn Sprite.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; img1: Handle&amp;lt;Image&amp;gt; = asset_server.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;img1.png&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(SpriteBundle {
&lt;&#x2F;span&gt;&lt;span&gt;        texture: img1.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add another Sprite with img2.png
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; img2: Handle&amp;lt;Image&amp;gt; = asset_server.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;img2.png&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(SpriteBundle {
&lt;&#x2F;span&gt;&lt;span&gt;        texture: img2.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此时当我们再次运行程序时，可以看到img2.png的内容在img1.png之上：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;040.png&quot; alt=&quot;040&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;不难理解，先生成的图片实体先渲染，后生成的图片实体后渲染。那么，假设我希望img1.png能够盖住img2.png呢？此时只需要将其位置组件的z轴设置为大于0即可（因为z轴默认是0）：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;startup&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;commands&lt;&#x2F;span&gt;&lt;span&gt;: Commands, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;asset_server&lt;&#x2F;span&gt;&lt;span&gt;: Res&amp;lt;AssetServer&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add camera.
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(Camera2dBundle::default());
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; then spawn Sprite.
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; img1: Handle&amp;lt;Image&amp;gt; = asset_server.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;load&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;img1.png&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(SpriteBundle {
&lt;&#x2F;span&gt;&lt;span&gt;        texture: img1.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span&gt;(),
&lt;&#x2F;span&gt;&lt;span&gt;      	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add transform
&lt;&#x2F;span&gt;&lt;span&gt;        transform: Transform {
&lt;&#x2F;span&gt;&lt;span&gt;            translation: Vec3 {
&lt;&#x2F;span&gt;&lt;span&gt;                z: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;1.&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; &amp;lt;- z &amp;gt; 0
&lt;&#x2F;span&gt;&lt;span&gt;              	x: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;100.&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;            ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        },
&lt;&#x2F;span&gt;&lt;span&gt;        ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;    });
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add another Sprite with img2.png
&lt;&#x2F;span&gt;&lt;span&gt;		&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;blockquote&gt;
&lt;p&gt;为了检查效果，笔者还将img1的位置进行一定的偏移。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;此时再次运行，可以看到img1已经在img2之上，盖住了img2：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;050.png&quot; alt=&quot;050&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;同时，通过上面x轴偏移的效果我们也不难知道，在Bevy中的2D的坐标是以&lt;strong&gt;视口&lt;&#x2F;strong&gt;（我们马上介绍什么是视口Viewport）中心为原点的，并非传统GUI的top-left为原点的坐标系统。&lt;&#x2F;p&gt;
&lt;p&gt;注意，视口并非窗口，只是在默认情况下，视口就是整个窗口范围。所以接下来，就让我们开始介绍视口的内容。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;shi-kou-viewport&quot;&gt;视口Viewport&lt;&#x2F;h1&gt;
&lt;p&gt;通过查阅Camera2dBundle中的Camera组件细节，我们会发现Camera组件存在一个名为&lt;code&gt;viewport&lt;&#x2F;code&gt;的字段。根据其文档我们可以知道，这个字段可以用来配置将Camera摄取到的内容渲染到指定&lt;code&gt;RenderTarget&lt;&#x2F;code&gt;的矩形区域：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;060.png&quot; alt=&quot;060&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;所以，在继续对视口相关内容进行介绍前，我们首先要知道上面提到的&lt;code&gt;RenderTarget&lt;&#x2F;code&gt;是个什么东西。继续翻阅Camera的代码内容，可以看到Camera还有一个字段&lt;code&gt;target&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;070.png&quot; alt=&quot;070&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;查看&lt;code&gt;RenderTarget&lt;&#x2F;code&gt;定义如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;080.png&quot; alt=&quot;080&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;通过其文档我们得知，“target”指的是Camera摄取的内容应该渲染到哪个目标上。已知的有：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Window窗体。这是默认的目标。&lt;&#x2F;li&gt;
&lt;li&gt;Image图片。也就是说我们有望将游戏世界中的内容摄取并输送到一张图片上。&lt;&#x2F;li&gt;
&lt;li&gt;纹理视图。这个暂时不在本文中进行讲解。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;在了解了上述的&lt;code&gt;RenderTarget&lt;&#x2F;code&gt;后，我们再回到&lt;strong&gt;视口viewport&lt;&#x2F;strong&gt;的相关内容。此时我们不难推断出，通过配置viewport，我们可以将Camera摄取到的内容输送到相关渲染目标的某个区域（毕竟无论是Window窗体还是Image图像等，它们都是一个平面2D的接收图像的区域罢了）。所以，接下来我们实践一下。将视口配置为一个 400 x 200 的区域：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-rust &quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;startup&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;commands&lt;&#x2F;span&gt;&lt;span&gt;: Commands, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;asset_server&lt;&#x2F;span&gt;&lt;span&gt;: Res&amp;lt;AssetServer&amp;gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add camera.
&lt;&#x2F;span&gt;&lt;span&gt;    commands.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;spawn&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        Camera2dBundle {
&lt;&#x2F;span&gt;&lt;span&gt;            camera: Camera {
&lt;&#x2F;span&gt;&lt;span&gt;              	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 配置视口的代码
&lt;&#x2F;span&gt;&lt;span&gt;                viewport: Some(Viewport {
&lt;&#x2F;span&gt;&lt;span&gt;                    physical_position: UVec2::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;50&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                    physical_size: UVec2::new(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;400&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;200&lt;&#x2F;span&gt;&lt;span&gt;),
&lt;&#x2F;span&gt;&lt;span&gt;                    ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;                }),
&lt;&#x2F;span&gt;&lt;span&gt;                ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;            },
&lt;&#x2F;span&gt;&lt;span&gt;            ..&lt;&#x2F;span&gt;&lt;span style=&quot;color:#96b5b4;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;()
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;    );
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; then spawn Sprite.
&lt;&#x2F;span&gt;&lt;span&gt;		&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; add another Sprite with img2.png
&lt;&#x2F;span&gt;&lt;span&gt; 		&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;上述代码中，我们给对应的Camera配置了视口：在渲染目标（目前就是当前这个窗口）的左上角（50，50）位置开始，宽400，高200的矩形区域。在其余代码保持不变的情况下，运行该程序能够得到如下的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;090.png&quot; alt=&quot;090&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;此时，我们可以用一张图例来更加形象的表达上述目前的情形：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;100.png&quot; alt=&quot;100&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-2dshi-ti-de-wei-zhi-deng-chu-li&quot;&gt;关于2D实体的位置等处理&lt;&#x2F;h2&gt;
&lt;p&gt;实际上，如果你对上面的内容了解了以后，就很容易知道应该如何处理2D实体的位置、缩放等。一般来说，我们可以有两种方式：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;调整世界中的实体的变换&lt;&#x2F;li&gt;
&lt;li&gt;调整摄像机的变换（transform）&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;假设整个世界中只有一个带有变换组件（Transform）的实体，假设我们希望对这个物体旋转180度，我们即可对这个实体的变换组件进行180旋转变换，也可以对摄像机进行180度的旋转变换。不过，从Bevy这一层次来说，要控制某个实体的位置大小等，最直观的还是对这个实体本身进行变换。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;duo-ge-camera&quot;&gt;多个Camera&lt;&#x2F;h1&gt;
&lt;p&gt;一般情况下，我们很容易的产生多个Camera：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;110.png&quot; alt=&quot;110&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在前面的代码基础上，我们又Spawn了另一个Camera，这个Camera的视口我们设置到了另一个区域。此时运行程序，我们会发现效果如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;120.png&quot; alt=&quot;120&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;如果你理解了前面的视口流程图，那么这个地方也不难理解：两个摄像机都在“摄取”游戏世界中的2D内容，但由于两个摄像机有不同的视口配置，因此将摄取到的内容渲染到渲染目标上Window上的时候，会将内容渲染到不同的位置：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;130.png&quot; alt=&quot;130&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;当然，从上图你会发现控制台有这样一句不断打印的警告：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;WARN bevy_render::camera::camera: Camera order ambiguities detected for active cameras with the following priorities: {(0, Some(Window(NormalizedWindowRef(Entity { index: 0, generation: 1 }))))}. To fix this, ensure there is exactly one Camera entity spawned with a given order for a given RenderTarget. Ambiguities should be resolved because either (1) multiple active cameras were spawned accidentally, which will result in rendering multiple instances of the scene or (2) for cases where multiple active cameras is intentional, ambiguities could result in unpredictable render results.
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;核心意思是，如果场景中存在多个Camera且对应的渲染对象都是一个（比如本例中的同一个Window），需要每一个Camera一个特定的顺序，否则这种歧义可能会造成不可预测的渲染结果（Bevy内部的机制导致的，这里不深究）。所以，为了消除这一警告，我们只需要为不同的Camera设置不同的&lt;code&gt;order&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;140.png&quot; alt=&quot;140&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bu-tong-de-she-xiang-ji-xuan-ran-bu-tong-de-shi-ti&quot;&gt;不同的摄像机渲染不同的实体&lt;&#x2F;h2&gt;
&lt;p&gt;有的时候，我们确实需要有多个Camera，且它们各自需要“摄取”并渲染不同的实体。此时，为了实现这个效果，我们需要引入一个组件：RenderLayer。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;150.png&quot; alt=&quot;150&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;我们将第一个Camera对应的实体和img1对应的实体都添加了&lt;code&gt;RenderLayer::layer(1)&lt;&#x2F;code&gt;，标识摄像机将只会“摄取”layer为1的所有实体。同样的，我们针对第二个Camera和img2添加&lt;code&gt;RenderLayer::layer(2)&lt;&#x2F;code&gt;：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;160.png&quot; alt=&quot;160&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;就绪以后，运行程序，我们能看到如下效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;170.png&quot; alt=&quot;170&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;左上角视口的Camera#1，只摄取了img1的内容；而右边的Camera#2则只摄取了img2的内容。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-yu-duo-camerade-shi-ji-ying-yong&quot;&gt;关于多Camera的实际应用&lt;&#x2F;h2&gt;
&lt;p&gt;上述内容，我们介绍了多Camera的一些效果。那么对于多Camera有什么实际的应用呢？笔者能想到有：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;ui和游戏世界渲染的分离&lt;&#x2F;li&gt;
&lt;li&gt;某些小地图的呈现&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;对于第一点来说，最直观的就是，也许我们制作的游戏类似这样的布局：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;180.png&quot; alt=&quot;180&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上述是游戏《Caves of Quad》的游戏界面,整体来看，我们就完全可以使用2个以上的Camera来分别渲染左侧的游戏部分和右侧的状态部分。&lt;&#x2F;p&gt;
&lt;p&gt;对于第二点来说，像CS2中的投掷物预览效果，就可以单独用一个Camera来呈现：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-09-26&#x2F;190.png&quot; alt=&quot;190&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本文的代码仓库为：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;bevy_examples&quot;&gt;w4ngzhen&#x2F;bevy_examples (github.com)&lt;&#x2F;a&gt;。同时，后续我会添加更多的示例供读者参考。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;xiang-guan-yue-du-yi-ji-can-kao&quot;&gt;相关阅读以及参考&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bevy-cheatbook.github.io&#x2F;graphics&#x2F;camera.html&quot;&gt;Cameras - Unofficial Bevy Cheat Book (bevy-cheatbook.github.io)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;tree&#x2F;latest&#x2F;examples&quot;&gt;bevy&#x2F;examples at latest · bevyengine&#x2F;bevy (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>【翻译转载】如果你想要构建一套基于ECS的GUI框架</title>
        <published>2024-08-21T00:00:00+00:00</published>
        <updated>2024-08-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/【翻译转载】如果你想要构建一套基于ECS的GUI框架/"/>
        <id>https://zhen.wang/article/【翻译转载】如果你想要构建一套基于ECS的GUI框架/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/【翻译转载】如果你想要构建一套基于ECS的GUI框架/">&lt;h1 id=&quot;yi-zhe-xu&quot;&gt;译者序&lt;&#x2F;h1&gt;
&lt;p&gt;本文原文地址：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.leafwing-studios.com&#x2F;blog&#x2F;ecs-gui-framework&#x2F;&quot;&gt;So you want to build an ECS-backed GUI framework | Leafwing Studios (leafwing-studios.com)&lt;&#x2F;a&gt;。翻译和发布本文前，已经获得了原作者的许可。&lt;&#x2F;p&gt;
&lt;p&gt;本人翻译这篇文章的主要是因为尽管该文是从 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 这一具体的库出发，但其核心是讨论GUI框架的设计思路与范式，同时还介绍了很多已有成果，个人觉得如果有读者对基于Rust的GUI建设感兴趣的话，一定会从这篇文章中学到很多有价值的内容。当然，笔者只是利用自己欠佳的英语水平结合翻译软件来对原文进行了翻译，所以如有翻译上的问题，还请读者指出，感激不敬！&lt;&#x2F;p&gt;
&lt;p&gt;此外，这篇文章由原作者于23年11月编写，当时 &lt;strong&gt;bevy&lt;&#x2F;strong&gt; 正值0.12版本，在翻译这篇文章的时候已经是24年的8月底，bevy的版本已经更新到了0.14，所以文中关于 &lt;strong&gt;bevy&lt;&#x2F;strong&gt; 、&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 的一些建设工作（如PR、讨论等）不一定具有时效性。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;原文标题为：&quot;So you want to build an ECS-backed GUI framework&quot;，从文章上下文理解看来，实际上应该是&quot;So (if) you want to build an ECS-backed GUI framework&quot;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h1 id=&quot;zheng-wen&quot;&gt;正文&lt;&#x2F;h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Challenges and opportunities in the future of &lt;code&gt;bevy_ui&lt;&#x2F;code&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;关于 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 未来的挑战与机遇&lt;&#x2F;p&gt;
&lt;p&gt;Alice I. Cecile, Rose Peck  |  2023-11-27&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;如果你想要通过Rust（以及其生态）构建一个用户界面，那有什么工具能比&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Entity_component_system&quot;&gt;实体-组件-系统（ECS）框架&lt;&#x2F;a&gt;更合适呢？它不仅提供了一种类型安全的、流行的状态管理方案，而且最重要的是：这套体系的速度将非常快（显然无需进行基准测试）。&lt;&#x2F;p&gt;
&lt;p&gt;当然，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bevyengine.org&#x2F;&quot;&gt;Bevy&lt;&#x2F;a&gt; 确实在这么做！实际上，它已经这样做了好几年。那么，为何它没有在竞争中脱颖而出，赢得数百万人的心，并取代 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;areweguiyet.com&#x2F;&quot;&gt;areweguiyet.rs&lt;&#x2F;a&gt; 呢？&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;译者注：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;areweguiyet.com&#x2F;&quot;&gt;Are we GUI yet?&lt;&#x2F;a&gt;网站陈列了目前已知的所有基于Rust绑定或实现的GUI框架。这里的“取代”主要含义是为什么Bevy的基于ECS的GUI框架为什么做的并不出众，没有成为某种优秀的范例，进而让基于Rust的GUI框架&lt;strong&gt;结束&lt;&#x2F;strong&gt;百花齐放的局面。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;虽然基于ECS模式的GUI可能并不是传统方式，但现有技术表明这并非不可能。&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;SanderMertens&#x2F;flecs&quot;&gt;flecs&lt;&#x2F;a&gt;在&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.flecs.dev&#x2F;flecs&#x2F;flecsscripttutorial.html&quot;&gt;这篇文档中&lt;&#x2F;a&gt;将许多核心思想进行了实现，同时也像 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jkb0o&#x2F;belly&quot;&gt;belly&lt;&#x2F;a&gt;，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bytestring-net&#x2F;bevy-lunex&quot;&gt;bevy_lunex&lt;&#x2F;a&gt;，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Anti-Alias&#x2F;bevy_ui_dsl&quot;&gt;bevy_ui_dsl&lt;&#x2F;a&gt;，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;nicopap&#x2F;cuicui_layout&quot;&gt;cuicui_layout&lt;&#x2F;a&gt;以及&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;StarArawn&#x2F;kayak_ui&quot;&gt;kayak_ui&lt;&#x2F;a&gt;这些库进行了这方面的尝试，使得Bevy的ECS（模式）显现出了巨大的潜力。甚至还有一个名为Polyphony的，独立的，ECS先行的，基于Javascript编写的GUI库（关于它的讨论：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;traffaillac&#x2F;traffaillac.github.io&#x2F;issues&#x2F;1&quot;&gt;Polyphony ECS GUI and future · Issue #1 · traffaillac&#x2F;traffaillac.github.io&lt;&#x2F;a&gt;）。&lt;&#x2F;p&gt;
&lt;p&gt;事实证明，困扰&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy_ui&#x2F;latest&#x2F;bevy_ui&#x2F;&quot;&gt;bevy_ui&lt;&#x2F;a&gt;的大多数问题都不是由、由于使用ECS甚至使用Rust的决定引起的，而是一些无聊、乏味和令人沮丧的内容：编写GUI框架是一项涉及大量容易变动的工作。Bugs、模板代码和缺失的功能击碎了用户和开发人员逐步改进优化代码的意愿。&lt;&#x2F;p&gt;
&lt;p&gt;但在我们深入讨论繁杂的细节之前，有一个重要的免责声明。Alice是Bevy的维护者（译者注：同时也是本文的作者），但不是项目负责人，甚至不是UI方面的专家。Rose是Foresight Spatial Labs的一名员工，她日常工作是使用Bevy和传统的web框架（React）来构建重GUI的应用程序。本文相关的论点、意见纯粹是我们自己的，不是最终定性的或官方的话！&lt;&#x2F;p&gt;
&lt;p&gt;这篇文章旨在记录如何制作一个GUI框架，为什么我们要使用ECS，以及我们需要修复哪些部分才能使 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 变得更优秀。（关于这些内容）我们在很多地方（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;254&quot;&gt;这里&lt;&#x2F;a&gt;，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;9538&quot;&gt;这里&lt;&#x2F;a&gt;，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;5604&quot;&gt;这里&lt;&#x2F;a&gt;，以及&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discord.com&#x2F;channels&#x2F;691052431525675048&#x2F;743663673393938453&quot;&gt;这里&lt;&#x2F;a&gt;）都有过很多重复的讨论，但很少有实质性，落地的实践活动（除了&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pulls&#x2F;ickshonpe&quot;&gt;ickshonpe &lt;&#x2F;a&gt;，you rock）（译者注：这位&lt;em&gt;icksphonpe&lt;&#x2F;em&gt;给 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 的部分提交了大量的优化、修复代码）。说“bevy_ui应该像我最喜欢的ui框架一样工作”很容易，但实际上将其转化为可行的设计、达成共识并其构建出来却要困难得多。&lt;&#x2F;p&gt;
&lt;p&gt;通过编写一份关于需求、愿景和进展的最新、全面、通俗易懂的文档，我们希望Bevy社区能够团结起来解决 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 今天遇到的问题，彻底排除各种可能性，并为关键的缺失部分提出可靠的设计。&lt;&#x2F;p&gt;
&lt;p&gt;谁知道呢？也许十年后，你正在阅读这篇文章，梦想着编写自己的基于ECS驱动的GUI框架。&lt;&#x2F;p&gt;
&lt;p&gt;在我感到非常疲惫、厌倦的过往中，有三类常见的关于 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; “脱轨”（译者注：原文为&lt;code&gt;&quot;get derailed&quot;&lt;&#x2F;code&gt;）的讨论：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Bevy应该使用一套现有的GUI框架。&lt;&#x2F;li&gt;
&lt;li&gt;一个同时适用于游戏和应用程序的GUI框架是不可能的。&lt;&#x2F;li&gt;
&lt;li&gt;您无法在ECS中构建GUI框架。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;wei-shen-me-bu-zhi-jie-yong-egui-huo-shi-dioxus-huo-shi-tauri-huo-shi-iced-huo-shi-yew&quot;&gt;为什么不直接用&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;emilk&#x2F;egui&quot;&gt;egui&lt;&#x2F;a&gt;（或是&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;DioxusLabs&#x2F;dioxus&quot;&gt;dioxus&lt;&#x2F;a&gt;，或是&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;tauri-apps&#x2F;tauri&quot;&gt;tauri&lt;&#x2F;a&gt;，或是&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;iced-rs&#x2F;iced&quot;&gt;iced&lt;&#x2F;a&gt;，或是&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;yewstack&#x2F;yew&quot;&gt;yew&lt;&#x2F;a&gt;）？&lt;&#x2F;h2&gt;
&lt;p&gt;现在已经有非常多的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.logrocket.com&#x2F;state-rust-gui-libraries&#x2F;&quot;&gt;基于Rust的GUI框架&lt;&#x2F;a&gt;，其中一些甚至一直在积极地维护，编写文档以及增加基本功能！&lt;&#x2F;p&gt;
&lt;p&gt;社区已经为其中一些制作了&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mvlabat&#x2F;bevy_egui&quot;&gt;出色的crates&lt;&#x2F;a&gt;，像Foresight这样的公司甚至使用这些第三方GUI框架制作了&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;5522&quot;&gt;复杂的生产应用程序&lt;&#x2F;a&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;Bevy想编写我们自己的ui实现，显然是&quot;非我发明&quot;综合症的典型案例（译者注：重复造轮子）。当我们可以使用现有的解决方案来编写即将到来的 Bevy Editor 时，为什么要将稀缺的精力(和决策)用于此？毕竟，我们可以&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;9538#discussioncomment-6984809&quot;&gt;与Dioxus完成一次正式的合作&lt;&#x2F;a&gt;，以此省下很多年的工作量。&lt;&#x2F;p&gt;
&lt;p&gt;然而，以下是我们基于技术以及社会方面考虑的，认为Bevy不应该这样做的原因：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;与引擎的其他部分保持一致性是非常有价值的：
&lt;ol&gt;
&lt;li&gt;为新用户提供了更简单、更一致的学习上的体验。&lt;&#x2F;li&gt;
&lt;li&gt;这样做使得系统更容易维护。&lt;&#x2F;li&gt;
&lt;li&gt;将所有更改保存在同一个代码仓库中，从而消除了在存在依赖树的情况下，对于版本发布要更加小心谨慎处理的必要。&lt;&#x2F;li&gt;
&lt;li&gt;一致性让UI模块能够从引擎其他模块的改进中也受益，反之亦然。Cart相信许多挑战并非是UI模块独有的，我们对此表示赞同！&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Bevy已经为GUI库需要完成的许多核心任务提供了一个很好的解决方案。
&lt;ol&gt;
&lt;li&gt;渲染、状态管理、资产asset、输入、窗口、异步等等。&lt;&#x2F;li&gt;
&lt;li&gt;为什么我们要再次采用重复的、或许微妙不兼容的方法来完成这些任务？&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;将数据发送到外部UI框架和从外部UI框架接收数据本身就容易出错，逻辑会变得复杂，难以维护，并且充斥着很多样板代码。
&lt;ol&gt;
&lt;li&gt;我们不可避免的需要提供&lt;em&gt;集成层&lt;&#x2F;em&gt;来面对不匹配的数据模型。&lt;&#x2F;li&gt;
&lt;li&gt;这并不是UI模块独有的：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dimforge&#x2F;bevy_rapier&quot;&gt;bevy_rapier&lt;&#x2F;a&gt;在物理方面也遇到了类似的问题（尽管它仍然是一个优秀的库）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;打破目前“屏幕上的盒子（boxes on a screen）”这一UI标准的设计思路，将会使得现状变得更加困难。
&lt;ol&gt;
&lt;li&gt;世界空间UI（World-space UI）是游戏的一个关键功能，涉及到：单元叠加（unit overlays）、VR菜单、计算机屏幕等等。&lt;&#x2F;li&gt;
&lt;li&gt;游戏UI&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;forum.unity.com&#x2F;threads&#x2F;i-look-forward-to-a-better-ui-system.1156304&#x2F;&quot;&gt;往往希望与游戏世界状态紧密结合，并具有不同寻常的艺术效果&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;使用第三方解决方案编写自定义着色器来覆盖某些节点的行为会变得更加困难。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;现有的Rust GUI项目都没有很好地回答解决一个事实：借用检查器非常讨厌&lt;strong&gt;图&lt;&#x2F;strong&gt;这样数据结构以及讨厌拆分（数据）可变性。
&lt;ol&gt;
&lt;li&gt;通过添加&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;3742&quot;&gt;关系relations&lt;&#x2F;a&gt;，Bevy保证了一种在Rust中处理图数据关系的独特而强大的方法。&lt;&#x2F;li&gt;
&lt;li&gt;Bevy的系统是一个灵活的、无panic的、快速且可靠的解决方案，用于共享对世界状态的可变访问。这背后有很多黑魔法，亲爱的上帝，我们不想解释两次了（译者注：可以在&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;taintedcoders.com&#x2F;bevy&#x2F;archetypes&#x2F;&quot;&gt;这里&lt;&#x2F;a&gt;了解）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;其他项目不由Bevy项目运营。
&lt;ol&gt;
&lt;li&gt;我们的目标可能会有所不同：例如，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.egui.rs&#x2F;&quot;&gt;egui&lt;&#x2F;a&gt;是专注于简单、快速构建的UI，为了达到这一要求，它需要在性能和可定制性之间做出权衡取舍。&lt;&#x2F;li&gt;
&lt;li&gt;改动会变得更难协调：我们需要迁移PR，并且无法快速添加编辑器所需的功能。&lt;&#x2F;li&gt;
&lt;li&gt;上游的依赖库可能（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vislyhq&#x2F;stretch&#x2F;issues&#x2F;86&quot;&gt;再次&lt;&#x2F;a&gt;）被废弃。如果Bevy计划继续存在几十年，所依赖其他库的UI解决方案也会一并存在吗？&lt;&#x2F;li&gt;
&lt;li&gt;我们无法保证某一个关键依赖项的质量。&lt;&#x2F;li&gt;
&lt;li&gt;它给那些较小的所以依赖的第三方库带来了很大的维护压力，因为会让如此大的客户端向它们发出优化、fix的请求。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;许多建议提到的第三方GUI库由于它们通常依赖于C、C++或JavaScript等其他语言生态，使Bevy的构建和分发过程变得非常复杂。&lt;&#x2F;li&gt;
&lt;li&gt;不要太苛刻，但很多现有的Rust GUI解决方案，只是不是那么完美。
&lt;ol&gt;
&lt;li&gt;虽然我们有很多能够接受的选择，但它们都有不小的缺点。没有人真正成为赢家。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;areweguiyet.com&#x2F;&quot;&gt;Are we GUI yet?&lt;&#x2F;a&gt;说“根不深，但种子已经播下”（&quot;The roots aren&#x27;t deep but the seeds are planted.&quot;）是有原因的。&lt;&#x2F;li&gt;
&lt;li&gt;在内心深处，我们都知道我们可以做得更好，并且我们应该做得更好。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;喜欢第三方GUI解决方案的用户可以并且无论如何都会使用它们。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;我们会学习其他GUI框架吗？当然。我们会正式大规模采用它们吗？绝对不会。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;yi-tao-guikuang-jia-lai-tong-ling-suo-you&quot;&gt;一套GUI框架来统领所有？&lt;&#x2F;h2&gt;
&lt;p&gt;在讨论 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 时，另一个常见的友善问题是“我们真的能用一个UI框架来满足所有用户的需求吗”？&lt;&#x2F;p&gt;
&lt;p&gt;我看到了一些潜在的分歧：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;254#issuecomment-886235989&quot;&gt;应用程序UI vs 简单游戏UI vs 复杂游戏UI&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;254#issuecomment-850216295&quot;&gt;一些热爱CSS以及web应用开发的人 vs 恨它们的人&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;9538#discussioncomment-7388170&quot;&gt;程序化的程序员友好型GUI vs 资产驱动的艺术家友好型GUI&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;立即模式GUI vs 保留模式GUI&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;我相信你能想到更多，分歧很容易出现，也很有趣！理论上，我们可以&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;discussions.unity.com&#x2F;t&#x2F;why-is-unity-creating-yet-another-ui-system&#x2F;850204&quot;&gt;参考Unity&lt;&#x2F;a&gt;（译者注：原文“pull a Unity”，但译者觉得可以理解为参考Unity，意思是“再搞一个类似的”），并在Bevy中创建多个相互竞争的UI框架。但我们认为这将&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arstechnica.com&#x2F;information-technology&#x2F;2014&#x2F;10&#x2F;googles-product-strategy-make-two-of-everything&#x2F;&quot;&gt;非常糟糕&lt;&#x2F;a&gt;，因为：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;这对用户来说非常困惑。&lt;&#x2F;li&gt;
&lt;li&gt;它分散了开发人员的注意力。&lt;&#x2F;li&gt;
&lt;li&gt;对于用户来说，很难权衡到底要使用哪种解决方案。&lt;&#x2F;li&gt;
&lt;li&gt;在两个相互竞争的解决方案之间进行迁移会非常痛苦。&lt;&#x2F;li&gt;
&lt;li&gt;在同一个项目中使用多个解决方案从根本上是站不住脚的。&lt;&#x2F;li&gt;
&lt;li&gt;需要两倍的时间（如果你幸运的话）。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;幸运的是，“在多个用户群体的不同需求中找到正确的所需的东西”并不是UI独有的问题。我们有很好的工具在架构层面管理这一点：&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;这个问题其实算是一种假设，因为实际上已经在web开发上得到了解决：
&lt;ul&gt;
&lt;li&gt;我们不会争辩说web UI是否是有史以来最伟大的UI解决方案（它有许多明显和不明显的缺陷）。&lt;&#x2F;li&gt;
&lt;li&gt;但事实上人们已经成功构建了几乎任何一种你能想到的使用HTML&#x2F;CSS&#x2F;JavaScript来搭建的UI：网页、代码编辑器、游戏（浏览器和独立版）、CAD应用程序、终端等。有一个常见的笑话是“未来一切都是chrome（&amp;lt;化学元素&amp;gt;铬，但实际指Google的Chrome浏览器）”（谢谢&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.electronjs.org&#x2F;&quot;&gt;Electron&lt;&#x2F;a&gt;）&lt;&#x2F;li&gt;
&lt;li&gt;值得说明的是，web UI技术栈并不是为大多数用例设计的。可以说，它不是为他们中的任何一个单独设计的！&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;模块化：确保用户可以移除或留下解决方案中某些他们不喜欢（或喜欢）的部分。
&lt;ul&gt;
&lt;li&gt;组件、系统、插件和Rust库crate的feature特性都非常适用于模块化！&lt;&#x2F;li&gt;
&lt;li&gt;第三方UI库如今存在着，并将继续存在。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;可扩展性：确保内部构件可访问且可构建。
&lt;ul&gt;
&lt;li&gt;公共组件以及资源真的很有用。&lt;&#x2F;li&gt;
&lt;li&gt;想象一下，一个基于 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 的可交互扩展库组成的丰富生态系统，所有这些库都建立在我们的核心渲染、交互和布局规范之上。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;抽象设计中的渐进式披露（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.uxpin.com&#x2F;studio&#x2F;blog&#x2F;what-is-progressive-disclosure&quot;&gt;Progressive Disclosure&lt;&#x2F;a&gt;）。
&lt;ul&gt;
&lt;li&gt;widget部件是由节点构建的。&lt;&#x2F;li&gt;
&lt;li&gt;节点只是实体。&lt;&#x2F;li&gt;
&lt;li&gt;在整个过程中，没有什么能阻止你在较低的层次进行hook（译者注：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zh.wikipedia.org&#x2F;wiki&#x2F;%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B&quot;&gt;Hook&lt;&#x2F;a&gt;）。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;如果用户可以将相同的ECS和渲染工具用于从像素艺术平台到单元着色视觉小说（cell-shaded visual novels）再到PBR竞技场射击游戏的一切实现，那么我们就可以创造出一个足够灵活、舒适的，适合每个人的UI解决方案。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ecszhong-de-gui-bevy-uishi-ji-shang-shi-ru-he-gong-zuo-de&quot;&gt;ECS中的GUI：bevy_ui实际上是如何工作的？&lt;&#x2F;h2&gt;
&lt;p&gt;解决了这些常见的反对意见后，我们有望开始讨论如何实际构建我们的UI框架。让我们考虑一下我们的实际产品需求，这样我们就可以看到 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 的不足之处。&lt;&#x2F;p&gt;
&lt;p&gt;不幸的是，对我们来说GUI框架是极其复杂的“野兽”。其中某些部分是如此重要，以至于将它们排除会破坏整个系统：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;存储节点树（Storing a tree of nodes）
&lt;ol&gt;
&lt;li&gt;几乎每个优秀的UI范式都有一个或多个嵌套的元素树。&lt;&#x2F;li&gt;
&lt;li&gt;“节点”是这样的元素：UI中最小的不可再分割的原子。&lt;&#x2F;li&gt;
&lt;li&gt;你需要把这些数据存储在某个地方！&lt;&#x2F;li&gt;
&lt;li&gt;在 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中，节点树存储在世界World中：每个节点都是一个具有node组件的实体。&lt;&#x2F;li&gt;
&lt;li&gt;UI实体通过使用父组件和子组件连接在一起。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;布局（Layout）
&lt;ol&gt;
&lt;li&gt;一旦有了一组节点后，我们希望能够描述它们在屏幕上的位置。&lt;&#x2F;li&gt;
&lt;li&gt;只是简单地指定绝对大小和位置并不是很稳健：当添加、删除节点，或屏幕大小更改时，这样的布局可能会遭到破坏。&lt;&#x2F;li&gt;
&lt;li&gt;在 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中，通过&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy&#x2F;latest&#x2F;bevy&#x2F;ui&#x2F;struct.Style.html&quot;&gt;Style&lt;&#x2F;a&gt;组件指定布局（这名称得怪CSS，抱歉）。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 使用了&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;dioxuslabs&#x2F;taffy&quot;&gt;taffy&lt;&#x2F;a&gt;（Alice帮助维护！）：它支持&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;css-tricks.com&#x2F;snippets&#x2F;css&#x2F;a-guide-to-flexbox&#x2F;&quot;&gt;flexbox弹性盒子&lt;&#x2F;a&gt;和&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;css-tricks.com&#x2F;snippets&#x2F;css&#x2F;complete-guide-grid&#x2F;&quot;&gt;css-grid网格&lt;&#x2F;a&gt;的布局策略。&lt;&#x2F;li&gt;
&lt;li&gt;如果你不想受到Web布局算法的约束，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vizia&#x2F;morphorm&quot;&gt;morphorm&lt;&#x2F;a&gt;（在我们看来）是一个更好的选择。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;输入（Input）
&lt;ol&gt;
&lt;li&gt;以键盘按压、鼠标点击、鼠标移动、触摸屏点击、游戏手柄输入等形式收集用户输入。&lt;&#x2F;li&gt;
&lt;li&gt;通常与“拾取（picking）”相匹配：根据位置找出鼠标指针事件关联的元素。&lt;&#x2F;li&gt;
&lt;li&gt;理想情况下，为涵盖悬停、按下、释放和长按按钮等操作设计一套不错的抽象概念。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 依赖于 &lt;strong&gt;bevy_input&lt;&#x2F;strong&gt;，而 &lt;strong&gt;bevy_input&lt;&#x2F;strong&gt; 又从 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-windowing&#x2F;winit&quot;&gt;winit&lt;&#x2F;a&gt; 和 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;gilrs&#x2F;latest&#x2F;gilrs&#x2F;&quot;&gt;gilrs&lt;&#x2F;a&gt; 这些底层库中获得数据。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;文本（Text）
&lt;ol&gt;
&lt;li&gt;将字符串转换为像素以便于我们能够将它们绘制到屏幕上。&lt;&#x2F;li&gt;
&lt;li&gt;在所包含文本的UI节点的边界内排列这些文本。&lt;&#x2F;li&gt;
&lt;li&gt;精确的像素对渲染很重要，但大小尺寸作为节点布局的输入同样也重要。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 当前正使用 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;glyph_brush&quot;&gt;glyph_brush&lt;&#x2F;a&gt; 进行文字字符渲染。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;pop-os&#x2F;cosmic-text&quot;&gt;cosmic-text&lt;&#x2F;a&gt; 对非拉丁文字有更好的塑形（shaping）支持。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;窗口管理（Window management）
&lt;ol&gt;
&lt;li&gt;创建一个（或n个）窗口来绘制UI。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy&lt;&#x2F;strong&gt; 使用 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-windowing&#x2F;winit&quot;&gt;winit&lt;&#x2F;a&gt;，你也应该用它。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;渲染（Rendering）
&lt;ol&gt;
&lt;li&gt;将UI的元素绘制到用户屏幕上。&lt;&#x2F;li&gt;
&lt;li&gt;Bevy使用 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy_render&#x2F;latest&#x2F;bevy_render&#x2F;index.html&quot;&gt;bevy_render&lt;&#x2F;a&gt;，而其内部进而使用 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;wgpu&#x2F;latest&#x2F;wgpu&#x2F;index.html&quot;&gt;wgpu&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;如果你正在构建自己的Rust GUI框架，试试&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;linebender&#x2F;vello&quot;&gt;vello&lt;&#x2F;a&gt;！&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;状态管理（State management）
&lt;ol&gt;
&lt;li&gt;保持对UI状态的持久性跟踪。&lt;&#x2F;li&gt;
&lt;li&gt;填充的文本内容，单选按钮，动画进度，菜单是打开还是关闭，暗&#x2F;亮模式等。&lt;&#x2F;li&gt;
&lt;li&gt;在 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中，状态作为组件存储在实体上（或者少量的状态作为全局资源）。这工作得非常好！&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;数据传递（Data transfer）
&lt;ol&gt;
&lt;li&gt;将数据从UI传输到其他数据结构进行存储，反之亦然。&lt;&#x2F;li&gt;
&lt;li&gt;在Bevy的上下文中，“其他数据存储”是存储您所有游戏&#x2F;应用程序状态的ECS World世界。&lt;&#x2F;li&gt;
&lt;li&gt;数据绑定是一种用于自动化此过程的抽象：自动和精细地进行数据的变更传递。&lt;&#x2F;li&gt;
&lt;li&gt;目前，&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt;使用系统（system）在world其他部分来回发送数据。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;在此基础上，您可能希望 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 添加如下的功能：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;导航（Navigation，GUI页面上元素导航）
&lt;ol&gt;
&lt;li&gt;以有原则的离散的方式浏览GUI菜单元素：“tab键”是常见的键绑定。&lt;&#x2F;li&gt;
&lt;li&gt;导航对键盘和游戏手柄都非常有用。&lt;&#x2F;li&gt;
&lt;li&gt;传统应用程序的重要可访问性功能（accessibility，a11y）。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 对这块还没有第一方的解决方案。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;样式（Styling）
&lt;ol&gt;
&lt;li&gt;widget部件和节点具有大量主要是样式性的属性。&lt;&#x2F;li&gt;
&lt;li&gt;我们希望确保我们的应用程序具有一致的外观和体感，并能完成快速更换。&lt;&#x2F;li&gt;
&lt;li&gt;对于应用程序（尤其是移动应用程序）来说，我们期望能够达到原生外观和体感。&lt;&#x2F;li&gt;
&lt;li&gt;样式可能采取以下形式达成实际效果：
&lt;ol&gt;
&lt;li&gt;级联继承（如CSS）。&lt;&#x2F;li&gt;
&lt;li&gt;选择器（如CSS中的选择器，或您可能使用在 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中提供的查询能力编写选择器）。&lt;&#x2F;li&gt;
&lt;li&gt;全局主题，例如白天明亮模式以及夜间暗黑模式。&lt;&#x2F;li&gt;
&lt;li&gt;widget部件支持某些特定样式。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;样式通常需要有可预测的组合规则：当多个样式同时影响一个元素时会发生什么？&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 目前没有任何第一方的抽象设计。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;可组合、可重用Widget部件的抽象设计（An abstraction for composable, reusable widgets）
&lt;ol&gt;
&lt;li&gt;即使是简单的部件类型（单选按钮、文本输入框），代码编写使用起来也相当复杂！&lt;&#x2F;li&gt;
&lt;li&gt;用户应该能够一次性编写这些代码，然后在整个项目中重用（reuse）它们，从而提高开发效率和以及保持UI一致性。&lt;&#x2F;li&gt;
&lt;li&gt;部件可以由一个或多个节点&#x2F;元素组成。&lt;&#x2F;li&gt;
&lt;li&gt;每个部件的节点数量可以动态变化：想想不断增长的待办事项列表。&lt;&#x2F;li&gt;
&lt;li&gt;部件需要能够接受参数来更改其内容或行为。例如，创建一个具有可自定义文本的可重用按钮。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 目前使用&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy&#x2F;latest&#x2F;bevy&#x2F;ecs&#x2F;bundle&#x2F;trait.Bundle.html&quot;&gt;Bundle&lt;&#x2F;a&gt;类型，但由于无法处理多个节点，因此算是较为失败。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;动作行为抽象（Action abstractions）
&lt;ol&gt;
&lt;li&gt;撤销-重做。&lt;&#x2F;li&gt;
&lt;li&gt;可重新绑定热键。&lt;&#x2F;li&gt;
&lt;li&gt;命令选项板。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 对此没有第一方解决方案，甚至第三方解决方案也不成熟（抱歉！）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;可访问性（Accessibility）
&lt;ol&gt;
&lt;li&gt;为UI创建并暴露机器程序友好的API：读取状态、控制渲染&#x2F;显示、发送输入并检测这些输入更改时会发生什么。&lt;&#x2F;li&gt;
&lt;li&gt;通常能够对键盘导航进行hook。&lt;&#x2F;li&gt;
&lt;li&gt;提供API供屏幕阅读器等工具使用，这些工具提供了一个可供选择的用户界面，以满足残疾用户的需求。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_a11y&lt;&#x2F;strong&gt; hook到&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;AccessKit&#x2F;accesskit&quot;&gt;accesskit&lt;&#x2F;a&gt;，你的GUI框架也应该如此。&lt;&#x2F;li&gt;
&lt;li&gt;关于可访问性，存在很多事项需要讨论，但不幸的是，我们在这里没有具体事项统计。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;本地化（Localization）
&lt;ol&gt;
&lt;li&gt;存在不止一种用户语言：你需要一种方法来变更UI元素（尤其是文本），以满足喜欢不同语言的用户的需求。&lt;&#x2F;li&gt;
&lt;li&gt;有些语言是从右向左而不是从左向右阅读的，如果不考虑这一点，某些UI设计往往会倒退（backwards）。&lt;&#x2F;li&gt;
&lt;li&gt;图标和表情符号在不同的地方也有不同的文化含义。&lt;&#x2F;li&gt;
&lt;li&gt;说真的，用&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;fluent&quot;&gt;fluent&lt;&#x2F;a&gt;就行了。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;资产管理（Asset management）
&lt;ol&gt;
&lt;li&gt;UI经常使用预先渲染的图像或图标作为视觉效果，尤其是在游戏中。&lt;&#x2F;li&gt;
&lt;li&gt;你可能想要自定义的UI的效果和图标等，或者以自己的方式显示图像和视频。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 使用&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;bevy_asset&quot;&gt;bevy_asset&lt;&#x2F;a&gt;来完成它&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;动画（Animation）
&lt;ol&gt;
&lt;li&gt;小动画，特别是一些UI元素发生变化时的小动画，可以显著提高UI的精致度和丰富性。&lt;&#x2F;li&gt;
&lt;li&gt;折叠&#x2F;展开上下文菜单，滑动抽屉，旋转加载图标，淡入&#x2F;淡出等动画效果。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 理论上与&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;bevy_animation&quot;&gt;bevy_animation&lt;&#x2F;a&gt;集成在一起，但集成还没有完善（译者注：翻译这篇文章的时候，已经集成发布了）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;调试工具（Debug tools）
&lt;ol&gt;
&lt;li&gt;渲染后快速检测，修改UI节点树。&lt;&#x2F;li&gt;
&lt;li&gt;调试工具对于定位bug和调试过程中摆弄（twiddling）样式非常有用。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 对此没有解决方案，但 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jakobhellermann&#x2F;bevy-inspector-egui&quot;&gt;bevy_inspector_egui&lt;&#x2F;a&gt; 做得很好。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;UI序列化（内存中对象到文件）和反序列化（文件到内存中对象）(UI serialization (in-memory object to file) and deserialization (file to in-memory object))
&lt;ol&gt;
&lt;li&gt;如果我们可以根据存储在文件中的定义并构建UI，那么我们可以：
&lt;ol&gt;
&lt;li&gt;使得外部工具（如游戏编辑器）更容易构建UI。&lt;&#x2F;li&gt;
&lt;li&gt;让客户端用户更容易自定义UI（想想Greasemonkey和游戏模组）。&lt;&#x2F;li&gt;
&lt;li&gt;构建调试工具会更容易实现和使用。&lt;&#x2F;li&gt;
&lt;li&gt;减少编译时间：只需热更新UI资产。&lt;&#x2F;li&gt;
&lt;li&gt;允许完全控制用于定义对象的格式和语法（译者注：比如创造DSL）。&lt;&#x2F;li&gt;
&lt;li&gt;提供了更好的模块化工具的潜力，可以在不修改源代码的情况下创建&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;3877&quot;&gt;更高级别的抽象和自动化迁移&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;在游戏中，这被称为“数据驱动”方式。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 当前使用场景scenes（来自&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy_scene&#x2F;latest&#x2F;bevy_scene&#x2F;index.html&quot;&gt;bevy_scene&lt;&#x2F;a&gt;）来实现UI序列化和反序列化。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;异步任务（Asynchronous tasks）
&lt;ol&gt;
&lt;li&gt;有的任务工作是由UI触发的，但是需要很长时间才能完成。&lt;&#x2F;li&gt;
&lt;li&gt;当发生上述情况时，你肯定不希望你的程序处于一直UI冻结的状态。&lt;&#x2F;li&gt;
&lt;li&gt;在 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中，使用 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy_tasks&#x2F;latest&#x2F;bevy_tasks&#x2F;index.html&quot;&gt;bevy_tasks&lt;&#x2F;a&gt; 来实现。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h2 id=&quot;wei-shen-me-bevy-ui-hen-zao-gao&quot;&gt;为什么 bevy_ui 很糟糕？&lt;&#x2F;h2&gt;
&lt;p&gt;通过hook到功能齐全（但尚未完成）的游戏引擎Bevy，bevy_ui 实际上在大多数这些领域都有初步的解决方案！&lt;&#x2F;p&gt;
&lt;p&gt;那么，为什么绝大多数人认为它比Bevy更像Bavy呢（more Bavy than Bevy）？在听取和整理了用户使用 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 的体验后，以下是 bevy 0.12版本中，按照对用户体验的主观印象进行排列的，关于 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 的关键问题：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;生成具有大量自定义属性的实体需要大量的样板：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;无限嵌套的以及随处可见的&lt;code&gt;..Default::default()&lt;&#x2F;code&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;当处理树中&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;blob&#x2F;v0.12.0&#x2F;examples&#x2F;ui&#x2F;ui.rs&quot;&gt;排列的多个实体时&lt;&#x2F;a&gt;，情况会变得更糟。如前所述，你不能在此场景使用bundle。&lt;&#x2F;li&gt;
&lt;li&gt;数据驱动的工作流并没有被广泛使用，因为Bevy的场景冗长且文档不足。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Bevy需要为UI部件拥有一套真正的抽象：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;并非所有widget部件都可以有意义地表示为单个实体。&lt;&#x2F;li&gt;
&lt;li&gt;Bevy提供了很少的预构建好的widget部件：我们只有按钮和图像。&lt;&#x2F;li&gt;
&lt;li&gt;因为我们缺乏标准化的抽象，即使&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;7116&quot;&gt;添加最简单、最有用的widget部件&lt;&#x2F;a&gt;也会引起争议并陷入困境。（需要明确的是，这不是审稿人或作者的错）&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;在schedule中使用系统不太适合数据绑定：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;UI的行为几乎总是一次性（one-off）的或非常离散稀疏的（very sparse）。（译者注：而ECS体系下的系统总是每一轮都在进行执行）&lt;&#x2F;li&gt;
&lt;li&gt;从UI侧启动的任务要么通常很小，要么通常将其放入异步任务池中。&lt;&#x2F;li&gt;
&lt;li&gt;我们真的希望能够引用一个单一的特定实体及其父实体和子实体。
&lt;ol&gt;
&lt;li&gt;解决这个问题需要创建几十个标记组件（Marker Component）：几乎每个按钮、文本框、图像、容器等都有一个。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;99%的时间，这些处理UI事件的系统不会工作，比较浪费时间，因为schedule模块必须不断轮询以查看是否需要做任何事情。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;在 bevy_ecs 中管理和遍历层次结构（向上或向下）真的很糟糕：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;3742&quot;&gt;Relations关系&lt;&#x2F;a&gt;管理短时间还不会实现。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Bevy的UI输入处理非常的原始：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;用于处理指针输入的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy&#x2F;latest&#x2F;bevy&#x2F;ui&#x2F;enum.Interaction.html&quot;&gt;Interaction交互&lt;&#x2F;a&gt;组件使用起来非常&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;7371&quot;&gt;有限&lt;&#x2F;a&gt;了。&lt;&#x2F;li&gt;
&lt;li&gt;对移动端&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;15&quot;&gt;多点触控&lt;&#x2F;a&gt;支持也非常&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;2333&quot;&gt;有限&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;rfcs&#x2F;pull&#x2F;41&quot;&gt;键盘和游戏手柄导航&lt;&#x2F;a&gt;目前是缺乏支持的（译者注：翻译此文章的时候已经支持了）。&lt;&#x2F;li&gt;
&lt;li&gt;对于可配置的按键绑定，没有第一方&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;leafwing-studios&#x2F;leafwing-input-manager&quot;&gt;动作行为抽象&lt;&#x2F;a&gt;支持。&lt;&#x2F;li&gt;
&lt;li&gt;Bevy的“picking选取”支持非常简单，且不容易扩展到非矩形元素或世界空间中的元素。（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;bevy_mod_picking&quot;&gt;bevy_mod_picking&lt;&#x2F;a&gt;加油！）&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Flexbox弹性盒子布局（以及小范围的CSS Grid网格）很难学习，有令人沮丧的边缘案例情况，还有糟糕的API。你能解释一下&lt;code&gt;flex-basis&lt;&#x2F;code&gt;是什么吗？&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;由于不久前的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10537&quot;&gt;修复错误&lt;&#x2F;a&gt;，&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中的字体渲染有时非常难看。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Bevy缺失了样式抽象：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;如今我们已经完成了实现：只需修改组件即可！&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;为 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 添加非常规的视觉效果太难了：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;我们&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;8973&quot;&gt;缺少圆角&lt;&#x2F;a&gt;：这对美观的UI至关重要。（它们目前在UI方面非常流行。我们可以等几年让它们过时，但无论如何，它们都会在几年后回来。）&lt;&#x2F;li&gt;
&lt;li&gt;我们也没有阴影，但没人在乎。&lt;&#x2F;li&gt;
&lt;li&gt;我们缺少九宫格布局支持（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10588&quot;&gt;nine-patch support&lt;&#x2F;a&gt;）：这对美观但也灵活的基于资产定义的UI至关重要。&lt;&#x2F;li&gt;
&lt;li&gt;在Bevy 0.12的UI材质（功能发布）之前，没有另外一条路可以让你在 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中添加自己的渲染抽象。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;用纯代码或通过手工编写场景文件来构建UI可能会很痛苦且容易出错：如果有一款可视化编辑器将会很棒。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;5476&quot;&gt;世界空间的UI&lt;&#x2F;a&gt;的支持程度很差，并且使用了一套&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;blob&#x2F;v0.12.0&#x2F;examples&#x2F;2d&#x2F;text2d.rs&quot;&gt;完全不同的工具&lt;&#x2F;a&gt;。&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;这对于游戏（生命条、单位帧）至关重要，对于GIS或CAD应用程序中的标记和标签等也非常有用。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt;没有对动画的一级支持。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt;节点都有&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy&#x2F;latest&#x2F;bevy&#x2F;transform&#x2F;components&#x2F;struct.Transform.html&quot;&gt;Transform&lt;&#x2F;a&gt;和&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy&#x2F;latest&#x2F;bevy&#x2F;transform&#x2F;components&#x2F;struct.GlobalTransform.html&quot;&gt;GlobalTransform&lt;&#x2F;a&gt; 组件，但不允许开发者使用它们。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;在Bevy中处理异步任务的人体工程学设计是令人沮丧的：需要对执行的任务进行手写代码跟踪和轮询太多。&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;在这些问题中，只有1（实体生成样板代码过多）、2（widget部件的抽象不足）、3（系统不适合UI事件回调）以及4（UI节点层次结构处理令人痛苦）是由于我们选择使用ECS架构而引起的。其余的都是标准的GUI问题：无论你使用什么范式，都需要解决这些问题。关键的是，每一个与ECS相关的问题都是Bevy应该为其他用例修复的：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;生成自定义实体（尤其是实体组合）对于常规的游戏代码来说很糟糕，场景scene也不是最佳实践。例如，我们生成一个玩家及其所有武器。&lt;&#x2F;li&gt;
&lt;li&gt;Bevy缺少一种涵盖多实体层次结构的代码定义级别的抽象：bundle也支持的不够好。&lt;&#x2F;li&gt;
&lt;li&gt;一次性系统适用于各种定制的复杂逻辑，我们需要创造一套开发模式来有效地使用它们。&lt;&#x2F;li&gt;
&lt;li&gt;Bevy的对于处理层级继承等部分的实现从根本上来说是缓慢、脆弱和痛苦的。Relations关系设计需要首要一级支持。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;ECS和GUI之间没有根本上的模式不匹配或架构不兼容。&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 并不是一个有根本缺陷的概念设计，只是它的ECS部分的基础还不够好。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;bevy-uide-qian-jin-zhi-lu&quot;&gt;bevy_ui的前进之路&lt;&#x2F;h2&gt;
&lt;p&gt;让 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 真正强大还有很长的路要走，但我们可以一步一步脚踏实地。尽管我们面临着一些悬而未决的问题，以及即将对核心组件进行重写的事项，但这并不意味着 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中的所有内容都应该被毁灭移除。GUI框架涉及大量复杂的，独立的子组件：一个领域模块的改进不会因其他领域模块的重写而无效！&lt;&#x2F;p&gt;
&lt;p&gt;我们可以把要做的工作分为三类：毫无争议的部分，有争议的部分以及待研究的部分。&lt;&#x2F;p&gt;
&lt;p&gt;没有争议直截了当的部分只需要解决掉它们即可。这些任务可能很容易，也可能比较困难，但对于如何或是否应该这样做，不应该有太多的分歧。就目前，这类事项包括：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;检视以及合并&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;8973&quot;&gt;UI圆角支持&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;检视以及合并&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10588&quot;&gt;九宫格布局支持&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;检视以及合并&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;4482&quot;&gt;实现动画插值与混合能力的Animatable特性&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;检视以及合并&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10702&quot;&gt;winit更新&lt;&#x2F;a&gt;，它包含了对各种BUG的修复以及功能优化。&lt;&#x2F;li&gt;
&lt;li&gt;最后，检视并合并&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10193&quot;&gt;将ab_glyph迁移为cosmic-text的PR&lt;&#x2F;a&gt;，该部分解锁了系统字体和复杂字体的使用。&lt;&#x2F;li&gt;
&lt;li&gt;添加对&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;5476&quot;&gt;世界空间UI&lt;&#x2F;a&gt;的支持，开始着手查看和合并&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10559&quot;&gt;基于摄像机驱动的UI的PR&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;添加对&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;6956&quot;&gt;UI不透明度&lt;&#x2F;a&gt;变化的支持。&lt;&#x2F;li&gt;
&lt;li&gt;添加更多关于 bevy_scene 的文档、示例和测试，使其更容易扩展和学习。&lt;&#x2F;li&gt;
&lt;li&gt;为Bevy中的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;15&quot;&gt;多点触控输入&lt;&#x2F;a&gt;添加更好的示例和功能。&lt;&#x2F;li&gt;
&lt;li&gt;改进Bevy中关于处理异步任务的人体工程学设计。&lt;&#x2F;li&gt;
&lt;li&gt;在taffy中添加&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;DioxusLabs&#x2F;taffy&#x2F;issues&#x2F;308&quot;&gt;Morphorm&lt;&#x2F;a&gt;和&#x2F;或&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;cuicui.nicopap.ch&#x2F;layout&#x2F;index.html&quot;&gt;cuicui_layout&lt;&#x2F;a&gt;布局策略，并在Bevy中暴露出来。&lt;&#x2F;li&gt;
&lt;li&gt;添加数十个widget部件（但目前由于围绕良好的widget部件抽象还未达成共识）。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;有争议的事项是那些我们对其具备清晰的理解和广泛的共识，但完成它们会产生重大的架构影响的事项，比如：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;创建一套样式抽象设计，使其通过修改组件值来完成工作：
&lt;ol&gt;
&lt;li&gt;Alice写了一个非常古老的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;rfcs&#x2F;pull&#x2F;1&quot;&gt;RFC&lt;&#x2F;a&gt;来说明这是如何工作的，&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;UkoeHB&#x2F;bevy_kot&quot;&gt;bevy_kot&lt;&#x2F;a&gt;提供了一种样式级联的方式，viridia的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;viridia&#x2F;quill&quot;&gt;quill&lt;&#x2F;a&gt;的实验也有一个很好的提案。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;上游的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;kgv&#x2F;bevy_fluent&quot;&gt;bevy_fluent&lt;&#x2F;a&gt;，将会在bevy项目的保护之下进行长期的维护。&lt;&#x2F;li&gt;
&lt;li&gt;添加对&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;rfcs&#x2F;pull&#x2F;41&quot;&gt;键盘以及游戏手柄的导航支持&lt;&#x2F;a&gt;，并将其加入到 &lt;strong&gt;bevy_a11y&lt;&#x2F;strong&gt; 中。&lt;&#x2F;li&gt;
&lt;li&gt;为&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;7371&quot;&gt;如何处理指针事件和状态添加适当的抽象&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;li&gt;优化并实现&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;9538&quot;&gt;Cart的bsn提案&lt;&#x2F;a&gt;，以提高场景的可用性：
&lt;ol&gt;
&lt;li&gt;这是受到现有作品（如&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lib.rs&#x2F;crates&#x2F;cuicui_layout&quot;&gt;cuicui&lt;&#x2F;a&gt;、&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;jkb0o&#x2F;belly&quot;&gt;belly&lt;&#x2F;a&gt;和&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;polako-rs&#x2F;polako&quot;&gt;polako&lt;&#x2F;a&gt;）的启发并与之密切相关。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;2565&quot;&gt;添加类似bundle的抽象&lt;&#x2F;a&gt;，但适用于多级层次组合：
&lt;ol&gt;
&lt;li&gt;添加一个&lt;code&gt;bsn!&lt;&#x2F;code&gt;宏，以便更容易实例化Bevy实体，特别是使用较少样板代码的实体层次结构。&lt;&#x2F;li&gt;
&lt;li&gt;添加一种通过派生宏从结构体生成这些多层次实体的方法。&lt;&#x2F;li&gt;
&lt;li&gt;现有技术包括&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;bevy_proto&#x2F;latest&#x2F;bevy_proto&#x2F;&quot;&gt;bevy_proto&lt;&#x2F;a&gt;和&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;moonshine-spawn&quot;&gt;moonshine-spawn&lt;&#x2F;a&gt;。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;添加&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;1402&quot;&gt;插值颜色的方法&lt;&#x2F;a&gt;以提升UI动画效果。&lt;&#x2F;li&gt;
&lt;li&gt;创建一个&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;7876&quot;&gt;UI特定的变换类型&lt;&#x2F;a&gt;，以实现更快的布局和更清晰，类型更安全的API。&lt;&#x2F;li&gt;
&lt;li&gt;为 &lt;strong&gt;taffy&lt;&#x2F;strong&gt; 中添加在单个节点树中的混合布局策略的支持。&lt;&#x2F;li&gt;
&lt;li&gt;在&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;vleue&#x2F;bevy_easings&quot;&gt;bevy_easings&lt;&#x2F;a&gt;和&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;djeedai&#x2F;bevy_tweening&quot;&gt;bevy_tweening&lt;&#x2F;a&gt;这两个库完成后，添加对动画缓动（easing）&#x2F;tween的支持。&lt;&#x2F;li&gt;
&lt;li&gt;使用上游的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;leafwing-studios&#x2F;leafwing-input-manager&quot;&gt;leafwing-input-manager&lt;&#x2F;a&gt;库，用于提供对按键绑定的抽象。&lt;&#x2F;li&gt;
&lt;li&gt;使用上游的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;aevyrie&#x2F;bevy_mod_picking&quot;&gt;bevy_mod_picking&lt;&#x2F;a&gt;库，解锁高性能、灵活的元素选择。&lt;&#x2F;li&gt;
&lt;li&gt;实现&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;issues&#x2F;3742&quot;&gt;Relations&lt;&#x2F;a&gt;，并将其应用到 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 中。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;研究任务需要具备相关重要的设计专业知识，需要仔细考虑截然不同的提案，可能没有明确的要求需要干嘛：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;定义并实现一个&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;5604&quot;&gt;标准的UI widget部件抽象&lt;&#x2F;a&gt;：
&lt;ol&gt;
&lt;li&gt;可组合的：widget部件可以与其他部件组合以创建新的部件类型。&lt;&#x2F;li&gt;
&lt;li&gt;灵活的：我们应该能够使用这种抽象来支持从按钮到列表再到选项卡视图的所有内容。&lt;&#x2F;li&gt;
&lt;li&gt;可配置的：用户可以更改widget部件某些重要属性来达成效果，而无需重复创建自己的类型。&lt;&#x2F;li&gt;
&lt;li&gt;或许可以映射到一个或多个Bevy实体，使用普通系统就可以对widget部件进行动态更新。&lt;&#x2F;li&gt;
&lt;li&gt;可在Bevy场景之间进行序列化。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;弄清楚我们希望如何处理UI行为（和数据绑定），以避免因为使用ECS中的系统system而卷入其他的问题：
&lt;ol&gt;
&lt;li&gt;这是Alice创建&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;blob&#x2F;v0.12.0&#x2F;examples&#x2F;ecs&#x2F;one_shot_systems.rs&quot;&gt;一次性系统&lt;&#x2F;a&gt;的最初动机。&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;aevyrie&#x2F;bevy_eventlistener&quot;&gt;事件冒泡&lt;&#x2F;a&gt;和各种各样（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;viridia&#x2F;quill&quot;&gt;quill&lt;&#x2F;a&gt;、&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;UkoeHB&#x2F;bevy_kot&quot;&gt;bevy_kot&lt;&#x2F;a&gt;）的响应式UI尝试（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;crates.io&#x2F;crates&#x2F;futures-signals&quot;&gt;futures-signals&lt;&#x2F;a&gt;、&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;aevyrie&#x2F;bevy_rx&quot;&gt;bevy_rx&lt;&#x2F;a&gt;、&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;TheRawMeatball&#x2F;ui4&quot;&gt;u4&lt;&#x2F;a&gt;）似乎是有趣的潜在工具库。&lt;&#x2F;li&gt;
&lt;li&gt;虽然并不总是直接适用，但&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;raphlinus.github.io&#x2F;rust&#x2F;gui&#x2F;2022&#x2F;05&#x2F;07&#x2F;ui-architecture.html&quot;&gt;Raph Levien在Xilem上的帖子&lt;&#x2F;a&gt;很有趣，值得一读。&lt;&#x2F;li&gt;
&lt;li&gt;数据模型是一项关键的挑战：很容易陷入所有权问题。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;弄清楚如何将数据绑定逻辑集成到Bevy场景中：
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;pull&#x2F;10711&quot;&gt;Callback  as Asset&lt;&#x2F;a&gt;这个PR看起来很有希望。&lt;&#x2F;li&gt;
&lt;li&gt;Vultix&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bevyengine&#x2F;bevy&#x2F;discussions&#x2F;9538#discussioncomment-7667372&quot;&gt;提出&lt;&#x2F;a&gt;了一种用&lt;code&gt;.bsn&lt;&#x2F;code&gt;文件定义数据绑定的语法和策略。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;构建&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;orgs&#x2F;bevyengine&#x2F;projects&#x2F;12&quot;&gt;Bevy编辑器&lt;&#x2F;a&gt;，并实现使用编辑器构建GUI场景的能力：
&lt;ol&gt;
&lt;li&gt;这里存在一种“循环依赖”：&lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 越好，编辑器构建起来就越容易（，编辑器构建越容易，使用 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 构建GUI就越好用）&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;显然，还有很多工作要做！但关键的是，并没有什么是完全不可能的。如果我们（Bevy开发者社区）能够团结起来，一次一个稳步地解决这些问题，我们（Alice和Rose）真的认为 &lt;strong&gt;bevy_ui&lt;&#x2F;strong&gt; 总有一天会达到我们对引擎其他部分所期望的质量、灵活性和人体工程学的标准。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;感谢您的阅读：希望它具有教育意义、发人深省和&#x2F;或有趣。如果你想在未来阅读更多这样的内容，可以考虑注册我们的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.leafwing-studios.com&#x2F;mailing-list&#x2F;&quot;&gt;电子邮件列表&lt;&#x2F;a&gt;或订阅我们的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.leafwing-studios.com&#x2F;rss.xml&quot;&gt;RSS&lt;&#x2F;a&gt;。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;yi-zhe-xie-zai-zui-hou&quot;&gt;译者写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;尽管有翻译工具的帮助，但是为了读懂原作者的意思，还是需要结合Bevy这个库以及ECS概念。翻译这篇文章也花了一定的时间，感谢读者的耐心阅读。&lt;&#x2F;p&gt;
&lt;p&gt;本文也会同步发布到本人的博客上：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zhen.wang&quot;&gt;zhen.wang&lt;&#x2F;a&gt;，欢迎小伙伴访问。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>关于计算机图形学的一些介绍（01）基本要素与空间变换</title>
        <published>2024-07-31T00:00:00+00:00</published>
        <updated>2024-07-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/关于计算机图形学的一些介绍（01）基本要素与空间变换/"/>
        <id>https://zhen.wang/article/关于计算机图形学的一些介绍（01）基本要素与空间变换/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/关于计算机图形学的一些介绍（01）基本要素与空间变换/">&lt;h1 id=&quot;xie-zai-qian-mian&quot;&gt;写在前面&lt;&#x2F;h1&gt;
&lt;p&gt;笔者前段时间开启了一个新的系列《Wgpu图文详解》，在编写的过程中，发现使用wgpu只是应用层面的内容。要想很好的介绍wgpu，不得不将图形学中的一些理论知识进行讲解。但是放在《Wgpu图文详解》这个系列里又有点喧宾夺主之意，所以决定单独用另一个系列来放置关于图形学的一些内容。另外，本系列的内容并不是按照某种顺序来编写的，而是想到哪些要点就介绍一些，算是带有科普性质的知识笔记。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;笔者并非图形学专业人士，只是将目前理解到的一些内容整理成文，如有内容上的问题，还请读者指出。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;&lt;strong&gt;本文内容主要为概念介绍，所以不会有代码产生。&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;san-wei-shi-jie-ji-chu&quot;&gt;三维世界基础&lt;&#x2F;h1&gt;
&lt;p&gt;我们都知道，两点组成一直线，三线（三点）组成一平面。所以，假设在三维空间中有这样三个点：&lt;code&gt;(0, 1, 0)&lt;&#x2F;code&gt;、&lt;code&gt;(-1, 0, 0)&lt;&#x2F;code&gt;、&lt;code&gt;(1, 0, 0)&lt;&#x2F;code&gt;，通过简单的思考，我们知道它们可以组成一个没有厚度的三角形，当然，我们可以使用更多的点组成更多的面，来创造一个更加立体的物体：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;010-things.png&quot; alt=&quot;010-things&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于一个三维空间的物体，我们一般分为两个阶段来“感知”它。&lt;&#x2F;p&gt;
&lt;p&gt;第一阶段是“创造”这个物体。我们首先在空间的某处定义好一些“顶点”；然后，通过将这些“顶点”两两直线相连，我们会得到一个物体的框架；最后，我们给框架上的每一面“糊”上一层“纸”，就得到了一个不透明的立体物体。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;020-cube.png&quot; alt=&quot;020-cube&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;第二阶段则是“观察”这物体。在第一阶段，我们仅仅是将一个三维物体创造了出来，它存在于空间的某处。为了让我们感知到它的存在，我们会用眼睛从某些角度去看它，或使用一台摄像机将它拍摄并呈现在一张照片上。但无论哪种方式，我们会发现我们都将一个空间中三维的物体“投射”一个“面”上，即将三维物体“降维”到了二维面上。&lt;&#x2F;p&gt;
&lt;p&gt;当然，将三维物体投影到二维平面上，一般有两种投影方式：正射投影、透视投影，二者最大的区别在于透视投影需要考虑近大远小的成像机制。例如，上图的立方体场景下，我们在站在z轴上俯看立方，正射投影和透视投影会出现不同的效果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;030-projection.png&quot; alt=&quot;030-projection&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;从上图可以看出，透视投影明显是最符合我们通常认知的投影方式。对于三维世界有了基本的认识以后，让我们接下开始对图形学的一些内容进行介绍。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tu-xing-xue-de-yao-su&quot;&gt;图形学的要素&lt;&#x2F;h1&gt;
&lt;p&gt;在上节中，我们简单介绍了在现实的三维世界中如何构造并观察到一个三维物体。在计算机图形学中其实也并没有完全的跳出这个过程。在计算机图形学中同样会有点、线、面，以及最终要呈现到屏幕上的“投影”。我们将从本节开始，深入浅出计算机图形学的一些重要概念以及它们的核心作用。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ding-dian-vertex&quot;&gt;顶点 vertex&lt;&#x2F;h2&gt;
&lt;p&gt;什么是顶点？读者初识“顶点”这个词的时候，可能会觉得“顶点”就是上面三维物体在构建过程的第一步中，我们进行定义的各个“点”。这样的理解对但不完全对，因为几何物体上的各个点是&lt;strong&gt;狭义&lt;&#x2F;strong&gt;上的顶点，它们仅仅表示三维空间中的一些位置。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;040-only-points.png&quot; alt=&quot;040-only-points&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;而计算机图形学中的顶点vertex，则是一个包含有更多内容的数据合集，包括不限于该点的：位置坐标、颜色信息等（为了不然读者产生过多的疑惑，我们先只提较为理解两个属性）。也就说，几何中的顶点几乎等同于位置坐标，而计算机图形学中的顶点，除了位置坐标以外，还可以包含该点的颜色等其他信息。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;050-vertex-more-info.png&quot; alt=&quot;050-vertex-more-info&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;读者一定要记住，从现在开始，只要谈到了“顶点”，都指的是包含位置、颜色以及其他额外信息的整个顶点数据，如果仅仅是描述位置，我们一定使用全称：“顶点位置”。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;顶点包含颜色数据的意义是什么？笔者在第一次接触计算机图形学的时候，能够很自然的理解顶点包含位置坐标数据，但是对于包含颜色信息（甚至是法线信息等）百思不得其解，直到后来了解的越来越多以后，才渐渐的理解了这其中的奥妙。当然，这里笔者先卖个关子，等到后续的图元、片元介绍完成以后，再回过头来就能够很好的理解了。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;tu-yuan-primitive&quot;&gt;图元 primitive&lt;&#x2F;h2&gt;
&lt;p&gt;在计算机图形学中，图元（Primitives）是构成图像的基本元素，它们是图形渲染过程中最基础的几何形状。图元可以是点、线、多边形（如三角形或四边形）等。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;图元装配&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;要得到图元，我们需要将上一小节介绍的“顶点”作为输入，进行一系列的操作，才能得到图元，这个过程就叫做&lt;strong&gt;图元装配&lt;&#x2F;strong&gt;。让我们用更加形象的例子来理解这个过程。&lt;&#x2F;p&gt;
&lt;p&gt;假设现在有三个点：&lt;code&gt;(0, 1)&lt;&#x2F;code&gt;、&lt;code&gt;(-1, 0)&lt;&#x2F;code&gt;以及&lt;code&gt;(1, 0)&lt;&#x2F;code&gt;。这三个点可以组成什么图形呢？需要我们用不同的方式来看：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;点方式（Points）：每个顶点都作为一个单独的点来渲染。&lt;&#x2F;li&gt;
&lt;li&gt;线方式（Line）：连续的两个顶点形成一条线段。&lt;&#x2F;li&gt;
&lt;li&gt;三角形方式（Triangles）：每三个顶点组成一个三角形。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;下图是上述三种方式下，结合上面三个点得到图形结果：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;060-point-line-face.png&quot; alt=&quot;060-point-line-face&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;图元装配还远远不止上述提到的三种方式，根据顶点数据的不同，还有其他形式的装配方式&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;所以，读者现在应该能够理解图元装配的核心逻辑是，将n个顶点通过某种图元装配的方式来得到最终的图形（不同的装配方式往往伴随着不同的算法逻辑）。&lt;&#x2F;p&gt;
&lt;p&gt;当然，在生成图元的时候，还包含了一些操作，不过为了帮助读者更好的理解，我们暂时放一放，后面统一讲解。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pian-yuan-fragment&quot;&gt;片元 fragment&lt;&#x2F;h2&gt;
&lt;p&gt;在介绍片元前，我们需要先提到一个操作概念：光栅化。光栅化是将几何数据经过一系列变换后转换为像素，并呈现在显示设备上的过程。我们常见的显示设备是由物理像素点按照一定的宽高值组成一块完整的屏幕。也就是说，屏幕上的像素点不是“连续”的。然而，我们的图像是“连续”的，这就意味着对于几何图形，一条线，特别是非水平非垂直的线，这条线上的每一点我们总是需要通过一定的近似处理，来得到其在屏幕上的物理像素的坐标。&lt;&#x2F;p&gt;
&lt;p&gt;我们以呈现一个三角形为例。假设现在有下图三角形，从几何的角度来看，它的斜边没有任何的异常：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;070-a-triangle.png&quot; alt=&quot;070-a-triangle&quot; &#x2F;&gt;然而，我们的物理设备像素是整数值且有限，假设有一块分辨率为 20x20 的屏幕，为了呈现斜边，我们可能需要按照如下的形式来找到对应的像素点填色：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;080-rasterization-triangle.png&quot; alt=&quot;080-rasterization-triangle&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;注意看，笔者在几何三角形上选取了1个点，几何坐标为&lt;code&gt;(0.5, 0.5)&lt;&#x2F;code&gt;。在 20x20 的屏幕上，其屏幕坐标为&lt;code&gt;(10, 10)&lt;&#x2F;code&gt;。&lt;&#x2F;p&gt;
&lt;p&gt;光栅化逻辑就是对于几何图形上每一个“点”，在屏幕设备上找到对应的像素点的过程。对于光栅化的实现，就不在本文的讨论范围内了，对于这块感兴趣的同学可以自行查阅相关资料进行深入研究。&lt;&#x2F;p&gt;
&lt;p&gt;简单了解完光栅化后，让我们回到本节的核心：片元fragment。片元实际上就是图元primitive经过光栅化处理后的一个或多个像素大小的样本。在这有两点值得注意：&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;尽管叫做&lt;strong&gt;片元&lt;&#x2F;strong&gt;，但通常指的是一个或少许多个像素大小的单位。也就是说，图元是一个整体几何图形，经过光栅化会被分解为多个片元。&lt;&#x2F;li&gt;
&lt;li&gt;光栅化后得到的片元只是&lt;strong&gt;接近&lt;&#x2F;strong&gt;像素点，但并不完全等于像素点。片元是与像素相关的、待处理的数据集合，包括颜色、深度、纹理坐标等信息（深度和纹理坐标等先简单理解为一些额外数据，后续会讲解）。这个地方有点类似于前面我们说的，图形学中的顶点，并不是简单的几何的顶点，而是包含有顶点位置、颜色信息等的一个数据集合。&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;片元并非像素点，它只是接近像素点，所以通常来说，我们还会有一个步骤来对片元进行进一步的处理，好让它最终转换为屏幕上的像素点来进行呈现（此时基本就是带有rgba颜色的点了）。&lt;&#x2F;p&gt;
&lt;p&gt;至此，我们简单了解了图形学中的三个要素：顶点、图元与片元。本来，接下来的内容应该介绍渲染管线了。但是笔者思考以后始终觉得，直接搬出一些概念，还是不够直观，初学者很容易被劝退。所以在介绍渲染管线之前，笔者决定先介绍一下在图形学中的空间变换。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;tu-xing-xue-zhong-de-kong-jian-bian-huan&quot;&gt;图形学中的空间变换&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;mo-xing-kong-jian-yu-shi-jie-kong-jian&quot;&gt;模型空间与世界空间&lt;&#x2F;h2&gt;
&lt;p&gt;假设我们制作了一个边长为2的立方体，如下图所示：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;090-simple-cube-model.png&quot; alt=&quot;090-simple-cube-model&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;此时，这个立方体我们是在当前坐标系中创建出来的。所以它的各个点的位置就如上图所示（例如图中标识的三个点：&lt;code&gt;(1, 0, 1)&lt;&#x2F;code&gt;、&lt;code&gt;(1, 1, -1)&lt;&#x2F;code&gt;以及&lt;code&gt;(-1, 1, 1)&lt;&#x2F;code&gt;等）。&lt;&#x2F;p&gt;
&lt;p&gt;随后，我们将这个立方体放置到一个“世界”场景中，在此之前，“世界”场景中已经有了一个球体：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;100-a-ball-in-world.png&quot; alt=&quot;100-a-ball-in-world&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;为了不让他们重叠，我们将这个立方体先将边长从原来的2缩小到1个单位，然后放置到如下位置：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;110-cube-and-ball.png&quot; alt=&quot;110-cube-and-ball&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;注意看，此时我们的立方体的坐标在此刻与球体共存的世界中的ABC三个点的坐标就不再是原有的坐标，而是在这个“世界”下的坐标（&lt;code&gt;A(3, 0.5, -0.5)&lt;&#x2F;code&gt;、&lt;code&gt;B(3, 0, 0.5)&lt;&#x2F;code&gt;、&lt;code&gt;C(2, 1, 0.5)&lt;&#x2F;code&gt;），这个过程，其实就是将“模型空间”转换到“世界空间”（的坐标）的过程。&lt;&#x2F;p&gt;
&lt;p&gt;“模型空间”的意义是每个三维物体本身在自己的一个空间坐标系下（又叫“局部坐标空间”），我们去定义这个物体的三维数据的时候，不依赖于外部，而是一个纯粹的当前物体的一个空间。&lt;&#x2F;p&gt;
&lt;p&gt;然而，我们将一个物体创造好以后，我们&lt;strong&gt;一般&lt;&#x2F;strong&gt;都需要将它放置到一个和其他的物体在一起的一个地方，组成一个场景，使之更有意义，而这个地方就是“世界空间”，在这个过程中，我们一般会对某个单独的模型物体进行旋转、缩放等操作，以便让它更加协调的存在于这个“世界空间”中。&lt;&#x2F;p&gt;
&lt;h2 id=&quot;guan-cha-kong-jian&quot;&gt;观察空间&lt;&#x2F;h2&gt;
&lt;p&gt;当得到世界空间下的坐标的时候，我们会进一步将其变换为“观察空间”。介绍“观察空间”前，我们需要先引入一个角色：摄像机。既然我们已经将“世界”准备好了（例如上面的一个立方体加上一个球体的场景），我们总是需要“观察”它，不然就没有意义了。因此，我们就会需要一个类似“摄像机”的角色。这个摄像机会包含有3个要素：1、摄像机的位置；2、摄像机看向的目标方向；3、摄像机的向上方法。对于摄像机的位置和看向的目标方向容易理解，对于摄像机的向上方向，用下面的示例应该也很好理解了：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;120-camera-direction.png&quot; alt=&quot;120-camera-direction&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上图中，我们首先在空间的某处放置一个摄像机，让它“看向”一棵树，我们使用蓝色向量表示出这个方向；通过这个蓝色的向量的方向和摄像机所在的位置，我们可以确定唯一一个让蓝色向量垂直的平面。在这个平面上， 我们可以找到无限组相互垂直的向量（例如上图左右两个摄像机的的红、绿色向量，我们可以绕着蓝色向量方向转动，还能得到更多的红、绿向量），其中，我们会把红色的向量定义为摄像机的右向量，那么垂直于红、蓝向量平面的绿向量就是向上的向量。摄像机向上方向的不同，会导致图像摄像机拍摄的物体的向上方向不同。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意，这里的红绿蓝向量，其实和一些教程（例如《[摄像机 - LearnOpenGL CN (learnopengl-cn.github.io)](https:&#x2F;&#x2F;learnopengl-cn.github.io&#x2F;01 Getting started&#x2F;09 Camera&#x2F;)》）是不太一致的。本文只是从现实出发，使用一种容易理解的方式来介绍概念，并不是完全的考虑计算层面的事情。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;有了确定的摄像机以后，我们需要进行这样的操作。将摄像机和整个世界做一次整体的位移操作，让摄像机移动到原点，观察方向与Z轴重合，上方向与Y轴重合，同时让“世界”中的物体保持与摄像机相对不变：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;130-camera-transform.png&quot; alt=&quot;130-camera-transform&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;140-camera-transform-animation.gif&quot; alt=&quot;140-camera-transform-animation&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在完成移动以后，原先的物体的坐标在摄像机处于原点的坐标空间下有了新的坐标位置（例如，原本我们的球体最顶部的点坐标是&lt;code&gt;(0, 2, 0)&lt;&#x2F;code&gt;，经过将世界空间转变为观察空间，就成了&lt;code&gt;(0, 2, -2)&lt;&#x2F;code&gt;），而这个过程就是“世界空间”转变为“观察空间”，摄像机处于原点的坐标空间就是“观察空间”。&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么有观察空间？因为将摄像机放到原点的过程后，对于后续的观察投影处理很方便。&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;经过一系列的操作，我们已经将一个2x2x2的立方体，从模型空间变换到了观察空间：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;150-point-transform-flow.png&quot; alt=&quot;150-point-transform-flow&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意看上图立方体A点的左边经过一系列变换的结果&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;在观察空间的基础上（此时摄像机就在观察空间的原点），我们就会开始进行投影处理。正如一开始介绍的，投影一般来说分为两种：1）正射投影；2）透视投影。&lt;&#x2F;p&gt;
&lt;p&gt;对于上面我们创建的球体和立方体，摄像机放在原点，分别使用正摄像投影和透视投影的效果大致如下：&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;160-projection.png&quot; alt=&quot;160-projection&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;容易理解的是，经过投影以后，我们将三维立体的物体转变为了二维图像。原先三维空间中任意一个点，都“投射”到了二维空间上。如果我们将这个二维空间视为我们的显示器屏幕。那么很显然，我们需要将“观察空间”中某一个三维坐标点通过一定的方式的计算变换，得到在屏幕上某个具体位置的像素。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2024-07-31&#x2F;170-projection-to-screen.png&quot; alt=&quot;170-projection-to-screen&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;对于上图，原本在观察空间中的点&lt;code&gt;A&lt;&#x2F;code&gt;的坐标，会通过一定的上下文（摄像机的距离、FOV视野等）通过一定的数据计算，来得到&lt;code&gt;A&#x27;&lt;&#x2F;code&gt;，而这个&lt;code&gt;A&#x27;&lt;&#x2F;code&gt;是屏幕上的某个x、y均为整数的像素坐标。&lt;&#x2F;p&gt;
&lt;h1 id=&quot;xie-zai-zui-hou&quot;&gt;写在最后&lt;&#x2F;h1&gt;
&lt;p&gt;本文就大致介绍关于图形学中的一些基本要素，以及空间变换。在后面的文章中，会逐步介绍计算机图形学中的一些内容，例如渲染管线等。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Wgpu图文详解（01）窗口与基本渲染</title>
        <published>2024-06-05T00:00:00+00:00</published>
        <updated>2024-06-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Wgpu-图文详解（01）窗口与基本渲染/"/>
        <id>https://zhen.wang/article/Wgpu-图文详解（01）窗口与基本渲染/</id>
        
        <summary type="html">&lt;p&gt;这是关于Rust Wgpu的介绍的系列文章，基于 winit 0.30.0 与 Wgpu 0.20.0。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Rust winit 0.30.0版本简介</title>
        <published>2024-05-14T00:00:00+00:00</published>
        <updated>2024-05-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Rust winit 0.30.0版本简介/"/>
        <id>https://zhen.wang/article/Rust winit 0.30.0版本简介/</id>
        
        <summary type="html">&lt;p&gt;不久前，Rust著名的跨平台窗体管理库winit发布了它的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-windowing&#x2F;winit&#x2F;releases&#x2F;tag&#x2F;v0.30.0&quot;&gt;0.30.0&lt;&#x2F;a&gt;版本，较之前的0.2x.x版本，&lt;strong&gt;新增&lt;&#x2F;strong&gt;了19个的模块API，&lt;strong&gt;改动&lt;&#x2F;strong&gt;大约19个模块API，&lt;strong&gt;移除&lt;&#x2F;strong&gt;了大约8个模块API。可见本次升级改动之大，主要是对事件循环、窗口管理的重构。鉴于目前网上较多的文章都是基于0.2x版本的winit的代码，存在时效性问题，所以我决定写一篇文章，对winit的0.30.0版本做一个简单的介绍，同时也为后面的Rust Wgpu系列文章做铺垫。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Rust工作空间（workspace）实践</title>
        <published>2024-05-10T00:00:00+00:00</published>
        <updated>2024-05-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Rust工作空间（workspace）实践/"/>
        <id>https://zhen.wang/article/Rust工作空间（workspace）实践/</id>
        
        <summary type="html">&lt;p&gt;本文将介绍如何使用cargo workspace来管理多个package，并通过实践介绍workspace的一些基础场景下的使用、配置方式。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>基于Rust的Tile-Based游戏开发杂记（02）ggez绘图实操</title>
        <published>2024-03-16T00:00:00+00:00</published>
        <updated>2024-03-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/基于Rust的Tile-Based游戏开发杂记（02）ggez绘图实操/"/>
        <id>https://zhen.wang/article/基于Rust的Tile-Based游戏开发杂记（02）ggez绘图实操/</id>
        
        <summary type="html">&lt;p&gt;尽管ggez提供了很多相关特性的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;ggez&#x2F;ggez&#x2F;tree&#x2F;master&#x2F;examples&quot;&gt;demo&lt;&#x2F;a&gt;供运行查看，但笔者第一次使用的时候还是有很多疑惑不解。经过仔细阅读demo代码并结合自己的实践，逐步了解了ggez在不同场景下的绘图方式，在此篇文章进行一定的总结，希望能够帮助到使用ggez的读者。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>基于Rust的Tile-Based游戏开发杂记（01）导入</title>
        <published>2024-02-27T00:00:00+00:00</published>
        <updated>2024-02-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用Rust开发Tile-Based游戏（01）导入/"/>
        <id>https://zhen.wang/article/使用Rust开发Tile-Based游戏（01）导入/</id>
        
        <summary type="html">&lt;p&gt;关于使用Rust开发Tile-Based游戏系列文章。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>浅谈Rust数据所有权</title>
        <published>2024-01-30T00:00:00+00:00</published>
        <updated>2024-01-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/浅谈Rust数据所有权/"/>
        <id>https://zhen.wang/article/浅谈Rust数据所有权/</id>
        
        <summary type="html">&lt;p&gt;Rust的目标之一，是能够作为一门内存高效且内存安全的语言。本文我们将重点关注Rust关于“内存高效”的语言设计，让读者能够建立起对Rust的基本认知。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（七）详解macOS下基于CEF的多进程应用程序CMake项目搭建</title>
        <published>2023-12-12T00:00:00+00:00</published>
        <updated>2023-12-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（七）详解macOS下基于CEF的多进程应用程序CMake项目搭建/"/>
        <id>https://zhen.wang/article/使用CEF（七）详解macOS下基于CEF的多进程应用程序CMake项目搭建/</id>
        
        <summary type="html">&lt;p&gt;由于macOS下的应用程序结构导致了CEF这样的多进程架构程序在项目结构、运行架构上有很多细节需要关注，这一块的内容比起Windows要复杂的多，所以本文将会聚焦macOS下基于CEF的多进程应用架构的环境配置，并逐一说明了CMake的相关用法和CEF应用配置细节。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（六）— 解读CEF的cmake工程配置</title>
        <published>2023-10-11T00:00:00+00:00</published>
        <updated>2023-10-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（六）— 解读CEF的cmake工程配置/"/>
        <id>https://zhen.wang/article/使用CEF（六）— 解读CEF的cmake工程配置/</id>
        
        <summary type="html">&lt;p&gt;距离笔者的《使用CEF》系列的第一篇文章居然已经过去两年了，在这么长一段时间里，笔者也写了很多其它的文章，再回看《使用CEF（一）— 起步》编写的内容，文笔稚嫩，内容单薄是显而易见的（主要是教大家按部就班的编译libcef_dll_wrapper库文件）。笔者一直以来的个性就是希望自己学习到的知识，研究出的内容，踩过的坑能够及时的写出来，介绍给更多的小伙伴。&lt;&#x2F;p&gt;
&lt;p&gt;这篇文章产生的背景是最近笔者再一次仔细的阅读了CEF binary distribution（CEF二进制分发包）的工程代码以及根目录下的CMakeLists.txt文件的所了解到的东西，希望在本文能够让读者小伙伴对于CEF binary distribution的工程结构有一个较为清晰的了解。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>C与CPP常见编译工具链与构建系统简介</title>
        <published>2023-09-12T00:00:00+00:00</published>
        <updated>2023-09-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/C与CPP常见编译工具链与构建系统简介/"/>
        <id>https://zhen.wang/article/C与CPP常见编译工具链与构建系统简介/</id>
        
        <summary type="html">&lt;p&gt;笔者最近在研究CEF的CMake工程，心血来潮想要对各种编译工具链以及构建系统做一个简单的总结，于是就有了本文。本文不会讲解任何关于&lt;code&gt;C&#x2F;C++&lt;&#x2F;code&gt;语言方面的内容，主要&lt;code&gt;C&#x2F;C++&lt;&#x2F;code&gt;的编译出发，介绍各种编译工具链与构建系统的关系。此外，由于笔者水平有限，无法从非常专业的角度剖析&lt;code&gt;C&#x2F;C++&lt;&#x2F;code&gt;的语言特性与编译，仅做入门介绍，如有不正确的地方还请评论提出。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>浅谈基于QT的截图工具的设计与实现</title>
        <published>2023-08-31T00:00:00+00:00</published>
        <updated>2023-08-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/浅谈基于QT的截图工具的设计与实现/"/>
        <id>https://zhen.wang/article/浅谈基于QT的截图工具的设计与实现/</id>
        
        <summary type="html">&lt;p&gt;本人一直在做属于自己的一款跨平台的截图软件（&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;capi&quot;&gt;w4ngzhen&#x2F;capi(github.com)&lt;&#x2F;a&gt;），在软件编写的过程中有一些心得体会，所以有了本文。其实这篇文章酝酿了很久，现在这款软件有了雏形，也有空梳理并写下这篇循序渐进的介绍截图工具的设计与实现的文章了。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>从null-ls归档再看nvim的代码格式化与lint方案</title>
        <published>2023-07-25T00:00:00+00:00</published>
        <updated>2023-07-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从null-ls归档再看nvim的代码格式化与lint方案/"/>
        <id>https://zhen.wang/article/从null-ls归档再看nvim的代码格式化与lint方案/</id>
        
        <summary type="html">&lt;p&gt;由于null-lsp的归档和暂停更新，我们需要重新审视并思考还有哪些架构简单易于理解的插件配置方案。本文将介绍脱离null-ls插件体系下的代码格式化和lint的插件配置方案。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>详解prettier使用以及与主流IDE的配合</title>
        <published>2023-07-17T00:00:00+00:00</published>
        <updated>2023-07-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/详解prettier使用以及与主流IDE的配合/"/>
        <id>https://zhen.wang/article/详解prettier使用以及与主流IDE的配合/</id>
        
        <summary type="html">&lt;p&gt;很多前端小伙伴在日常使用prettier的时候都或多或少有一点疑惑，prettier在每一个IDE中究竟是怎样工作起来的，为什么配置有时候生效，有时又毫无效果。为了让我们的前端小伙伴更加熟悉这块，本文将对prettier在主流IDE中的使用过程一探究竟。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>详解nvim内建LSP体系与基于nvim-cmp的代码补全体系</title>
        <published>2023-07-12T00:00:00+00:00</published>
        <updated>2023-07-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/详解nvim内建LSP体系与基于nvim-cmp的代码补全体系/"/>
        <id>https://zhen.wang/article/详解nvim内建LSP体系与基于nvim-cmp的代码补全体系/</id>
        
        <summary type="html">&lt;p&gt;2023年，nvim以及其生态已经发展的愈来愈完善了。nvim内置的LSP（以及具体的语言服务）加上众多插件，可以搭建出支持各种类型语法检查、代码补全、代码格式化等功能的IDE。网络上关于如何配置的文章很多，但本人发现绝大多数的文章仅仅停留在配置本身，没有深入的解释这些插件的作用和它们之间的关系，这就导致了很多入门的小伙伴在配置、使用的过程中遇到各种问题也不知如何下手。本文将手把手，一步一步的演进并解释，帮助小伙伴了解这块的内容。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>lazy-nvim插件管理器基础入门</title>
        <published>2023-06-19T00:00:00+00:00</published>
        <updated>2023-06-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/lazy-nvim插件管理器基础入门/"/>
        <id>https://zhen.wang/article/lazy-nvim插件管理器基础入门/</id>
        
        <summary type="html">&lt;p&gt;一篇通过使用lazy.nvim进行nvim插件管理的入门笔记。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>macOS下由yarn与npm差异引发的Electron镜像地址读取问题</title>
        <published>2023-05-22T00:00:00+00:00</published>
        <updated>2023-05-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/macOS下由yarn与npm差异引发的Electron镜像地址读取问题/"/>
        <id>https://zhen.wang/article/macOS下由yarn与npm差异引发的Electron镜像地址读取问题/</id>
        
        <summary type="html">&lt;p&gt;记录macOS下由yarn与npm差异引发的Electron镜像地址读取问题&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>TypeScript必知三部曲（二）JSX的编译与类型检查</title>
        <published>2023-04-18T00:00:00+00:00</published>
        <updated>2023-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/TypeScript必知三部曲（二）JSX的编译与类型检查/"/>
        <id>https://zhen.wang/article/TypeScript必知三部曲（二）JSX的编译与类型检查/</id>
        
        <summary type="html">&lt;p&gt;在本三部曲系列的第一部中，我们介绍了TypeScript编译的两种方案(tsc编译、babel编译）以及二者的重要差异，同时分析了IDE是如何对TypeScript代码进行类型检查的。该部分基本涵盖了TypeScript代码编译的细节，但主要是关于TS代码本身的编译与类型检查。而本文，我们将着重讨论含有JSX的TypeScript代码（又称TSX）如何进行类型检查与代码编译的。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>TypeScript必知三部曲（一）TypeScript编译方案以及IDE对TS的类型检查</title>
        <published>2023-04-08T00:00:00+00:00</published>
        <updated>2023-04-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/TypeScript必知三部曲（一）TypeScript编译方案以及IDE对TS的类型检查/"/>
        <id>https://zhen.wang/article/TypeScript必知三部曲（一）TypeScript编译方案以及IDE对TS的类型检查/</id>
        
        <summary type="html">&lt;p&gt;TypeScript代码的编译过程一直以来会给很多小伙伴造成困扰，typescript官方提供tsc对ts代码进行编译，babel也表示能够编译ts代码，它们二者的区别是什么？我们应该选择哪种方案？为什么IDE打开ts项目的时候，就能有这些ts代码的类型定义？为什么明明IDE对代码标红报错，但代码有能够编译出来？&lt;&#x2F;p&gt;
&lt;p&gt;带着这些问题，我们由浅入深介绍&lt;strong&gt;TypeScript&lt;&#x2F;strong&gt;代码编译的两种方案以及&lt;strong&gt;我们日常使用IDE进行ts文件类型检查&lt;&#x2F;strong&gt;
的关系，让你今后面对基于ts的工程能够做到游刃有余。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>浅谈React与SolidJS对于JSX的应用</title>
        <published>2023-04-05T00:00:00+00:00</published>
        <updated>2023-04-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/浅谈React与SolidJS对于JSX的应用/"/>
        <id>https://zhen.wang/article/浅谈React与SolidJS对于JSX的应用/</id>
        
        <summary type="html">&lt;p&gt;React将JSX这一概念深入人心。但，并非只有React利用了JSX，VUE、SolidJS等JS库或者框架都使用了JSX这一概念。网上已经有大量关于JSX的概念与形式的讲述文章，不在本文的讨论范围。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>基于webpack与TypeScript的SolidJS项目搭建</title>
        <published>2023-03-22T00:00:00+00:00</published>
        <updated>2023-03-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/基于webpack与TypeScript的solidjs项目搭建/"/>
        <id>https://zhen.wang/article/基于webpack与TypeScript的solidjs项目搭建/</id>
        
        <summary type="html">&lt;p&gt;本文将讲述如何基于webpack与TypeScript搭建一个基础的支持less模块的solidjs项目。方便后续涉及到solidjs相关分析与讨论都可以基于本文的成果之上进行。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>低代码平台前端的设计与实现（四）组件大纲树的构建设计</title>
        <published>2023-03-05T00:00:00+00:00</published>
        <updated>2023-03-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/低代码平台前端的设计与实现（四）组件大纲树的构建设计/"/>
        <id>https://zhen.wang/article/低代码平台前端的设计与实现（四）组件大纲树的构建设计/</id>
        
        <summary type="html">&lt;p&gt;在上篇文章，我们已经设计了一个简单的设计态的Canvas，能够显示经过BuildEngine生成的ReactNode进行渲染。本文，我们将继续上一篇文章的成果，设计并实现一个能够显示组件节点大纲树的组件。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>低代码平台前端的设计与实现（三）设计态画布DesignCanvas的设计与实现</title>
        <published>2023-02-04T00:00:00+00:00</published>
        <updated>2023-02-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/低代码平台前端的设计与实现（三）设计态画布DesignCanvas的设计与实现/"/>
        <id>https://zhen.wang/article/低代码平台前端的设计与实现（三）设计态画布DesignCanvas的设计与实现/</id>
        
        <summary type="html">&lt;p&gt;上一篇文章，我们分析并设计了关于构建引擎BuildEngine的切面设计。本文我们将基于BuildEngine所提供的切面处理能力，在CustomCreateElementHandle中通过一些逻辑，来完成一个轻量级的设计器画布。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>一文详解如何在基于webpack5的react项目中使用svg</title>
        <published>2023-01-29T00:00:00+00:00</published>
        <updated>2023-01-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/一文详解如何在基于webpack5的react项目中使用svg/"/>
        <id>https://zhen.wang/article/一文详解如何在基于webpack5的react项目中使用svg/</id>
        
        <summary type="html">&lt;p&gt;本文主要讨论基于webpack5+TypeScript的React项目（cra、craco底层本质都是使用webpack，所以同理）在2023年的今天是如何在项目中使用svg资源的。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>【个人笔记】2023年搭建基于webpack5与typescript的react项目</title>
        <published>2023-01-26T00:00:00+00:00</published>
        <updated>2023-01-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/【个人笔记】2023年搭建基于webpack5与typescript的react项目/"/>
        <id>https://zhen.wang/article/【个人笔记】2023年搭建基于webpack5与typescript的react项目/</id>
        
        <summary type="html">&lt;h1 id=&quot;xie-zai-qian-mian&quot;&gt;写在前面&lt;&#x2F;h1&gt;
&lt;p&gt;由于我在另外的一些文章所讨论或分析的内容可能基于一个已经初始化好的项目，为了避免每一个文章都重复的描述如何搭建项目，我在本文会统一记录下来，今后相关的文章直接引用文本，方便读者阅读。此文主要为个人笔记，不会有太多的关于思路的描述；另外，本文仅仅描述如何搭建基础react项目，不涉及图片等资源的加载，关于图片等资源的处理，我会单独编写一期。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>掌握webpack（一）一张图让你明白webpack中output的filename、path、publicPath与主流插件的关系</title>
        <published>2022-12-31T00:00:00+00:00</published>
        <updated>2022-12-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/掌握webpack（一）一张图让你明白webpack中output的filename、path、publicPath与主流插件的关系/"/>
        <id>https://zhen.wang/article/掌握webpack（一）一张图让你明白webpack中output的filename、path、publicPath与主流插件的关系/</id>
        
        <summary type="html">&lt;p&gt;webpack的核心概念，放到2022年相信很多的小伙伴都已经非常清楚了。但是，对于webpack配置中的output.path、output.filename以及output.publicPath，还有很多小伙伴还不理解。本文讲围绕output.filename、output.path与output.publicPath，讲解它们的功能，并分析这些配置与webpack中常使用到的MiniCssExtractPlugin、HtmlWebpackPlugin等插件的关系。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>低代码平台前端的设计与实现（二）构建引擎BuildEngine切面处理设计</title>
        <published>2022-12-03T00:00:00+00:00</published>
        <updated>2022-12-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/低代码平台前端的设计与实现（二）构建引擎BuildEngine切面处理设计/"/>
        <id>https://zhen.wang/article/低代码平台前端的设计与实现（二）构建引擎BuildEngine切面处理设计/</id>
        
        <summary type="html">&lt;p&gt;上一篇文章，我们介绍了如何设计并实现一个轻量级的根据JSON的渲染引擎，通过快速配置一份规范的JSON文本内容，就可以利用该JSON生成一个基础的UI界面。本文我们将回到低开的核心—页面拖拉拽，探讨关于页面拖拉拽的核心设计器Designer的一些&lt;strong&gt;基本前置需求&lt;&#x2F;strong&gt;，也就是构建引擎BuildEngine切面处理设计。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>【个人笔记】Nestjs使用TypeORM注意点</title>
        <published>2022-11-25T00:00:00+00:00</published>
        <updated>2022-11-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/【个人笔记】Nestjs使用TypeORM注意点/"/>
        <id>https://zhen.wang/article/【个人笔记】Nestjs使用TypeORM注意点/</id>
        
        <summary type="html">&lt;p&gt;在Nestjs使用TypeORM还是有一些注意点。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>nestjs搭建HTTP与WebSocket服务</title>
        <published>2022-11-22T00:00:00+00:00</published>
        <updated>2022-11-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/nestjs搭建HTTP与WebSocket服务/"/>
        <id>https://zhen.wang/article/nestjs搭建HTTP与WebSocket服务/</id>
        
        <summary type="html">&lt;p&gt;最近在做一款轻量级IM产品，后端技术栈框架使用了nodejs + nestjs作为服务端。同时，还需要满足一个服务同时支持HTTP服务调用以及WebSocket服务调用，此文主要记录本次搭建过程，以及基本的服务端设计。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>【个人笔记】VBox7安装Debian网络下载慢问题处理</title>
        <published>2022-11-06T00:00:00+00:00</published>
        <updated>2022-11-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/【个人笔记】VBox7安装Debian网络下载慢问题处理/"/>
        <id>https://zhen.wang/article/【个人笔记】VBox7安装Debian网络下载慢问题处理/</id>
        
        <summary type="html">&lt;p&gt;使用镜像安装Debian的过程中，会安装一些常用的软件包。但在安装软件包的阶段，默认情况下会通过网络进行下载。即使配置了国内的镜像，但是由于网络问题依然很慢。这个时候需要的在安装阶段选择从默认的DVD媒体安装。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>【个人笔记】基于VirtualBox7的Debian11基础环境搭建</title>
        <published>2022-10-27T00:00:00+00:00</published>
        <updated>2022-10-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/【个人笔记】基于VirtualBox7的Debian11基础环境搭建/"/>
        <id>https://zhen.wang/article/【个人笔记】基于VirtualBox7的Debian11基础环境搭建/</id>
        
        <summary type="html">&lt;p&gt;本文主要是对在最新的VirtualBox7上搭建Debian11的笔记记录，方便后续个人回顾，同时搭配对配置的浅析。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>FLTK基于cmake编译以及使用（Windows、macOS以及Linux）</title>
        <published>2022-10-19T00:00:00+00:00</published>
        <updated>2022-10-19T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/FLTK基于cmake编译以及使用（Windows、macOS以及Linux）/"/>
        <id>https://zhen.wang/article/FLTK基于cmake编译以及使用（Windows、macOS以及Linux）/</id>
        
        <summary type="html">&lt;p&gt;最近因为一些学习的原因，需要使用一款跨平台的轻量级的GUI+图像绘制 C&#x2F;C++库。经过一番调研以后，最终从GTK+、FLTK中选出了FLTK，跨平台、够轻量。本文将在Windows、macOS以及Linux Debian三套操作系统环境，对FLTK进行编译，并搭建简单Demo。这其中也有少许的坑，也在此文进行记录。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>低代码平台前端的设计与实现（一）构建引擎BuildEngine的基本实现</title>
        <published>2022-09-18T00:00:00+00:00</published>
        <updated>2022-09-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/低代码平台前端的设计与实现（一）构建引擎BuildEngine的基本实现/"/>
        <id>https://zhen.wang/article/低代码平台前端的设计与实现（一）构建引擎BuildEngine的基本实现/</id>
        
        <summary type="html">&lt;p&gt;这两年低代码平台的话题愈来愈火，一眼望去全是关于低代码开发的概念，鲜有关于低代码平台的设计实现。本文将以实际的代码入手，逐步介绍如何打造一款低开的平台。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（五）— 在QT中集成CEF（2）基于CLion与CMake搭建环境</title>
        <published>2022-09-12T00:00:00+00:00</published>
        <updated>2022-09-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（五）— 在QT中集成CEF（2）基于CLion与CMake搭建环境/"/>
        <id>https://zhen.wang/article/使用CEF（五）— 在QT中集成CEF（2）基于CLion与CMake搭建环境/</id>
        
        <summary type="html">&lt;p&gt;在前文《使用CEF（四）— 在QT中集成CEF（1）：基本集成》中，我们使用VS+QT的插件搭建了一个基于QT+CEF的项目。时过境迁，笔者目前用的最多的就是CLion+CMake搭建C&#x2F;C++项目，并且CLion提供了对C&#x2F;C++强大的开发环境。此外，也想将CMake搭建QT项目作为一次实践，故由此文。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>TypeScript与Babel、webpack的关系以及IDE对TS的类型检查</title>
        <published>2022-08-14T00:00:00+00:00</published>
        <updated>2022-08-14T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/TypeScript与Babel、webpack的关系以及IDE对TS的类型检查/"/>
        <id>https://zhen.wang/article/TypeScript与Babel、webpack的关系以及IDE对TS的类型检查/</id>
        
        <summary type="html">&lt;p&gt;只要接触过ts的前端同学都能回答出ts是js超集，它具备静态类型分析，能够根据类型在静态代码的解析过程中对ts代码进行类型检查，从而在保证类型的一致性。那，现在让你对你的webpack项目（其实任意类型的项目都同理）加入ts，你知道怎么做吗？带着这个问题，我们由浅入深，逐步介绍&lt;strong&gt;TypeScript&lt;&#x2F;strong&gt;、&lt;strong&gt;Babel&lt;&#x2F;strong&gt;以及&lt;strong&gt;我们日常使用IDE进行ts文件类型检查&lt;&#x2F;strong&gt;的关系，让你今后面对基于ts的工程能够做到游刃有余。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>从零搭建react与ts组件库（二）less模块化与svg引入配置</title>
        <published>2022-08-11T00:00:00+00:00</published>
        <updated>2022-08-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从零搭建基于react与ts的组件库（二）配置less模块话与svg/"/>
        <id>https://zhen.wang/article/从零搭建基于react与ts的组件库（二）配置less模块话与svg/</id>
        
        <summary type="html">&lt;p&gt;在上一篇《从零搭建react+ts组件库（一）项目搭建与封装antd组件》介绍了使用webpack来搭建一个基于antd的组件库的基本框架，但是作为一个组件库，实际上还有很多的都还未引入，本篇将会补充less模块化以及svg引入的基本方式。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>《世嘉新人培训教材—游戏开发》2DGraphics1项目cmake构建</title>
        <published>2022-07-22T00:00:00+00:00</published>
        <updated>2022-07-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/《世嘉新人培训教材—游戏开发》2DGraphics1项目cmake构建/"/>
        <id>https://zhen.wang/article/《世嘉新人培训教材—游戏开发》2DGraphics1项目cmake构建/</id>
        
        <summary type="html">&lt;p&gt;《世嘉新人培训教材—游戏开发》作为经典的游戏开发教程，提供了相关样例代码供我们进行开发使用。但是该样例是基于VS进行编写构建的，而本人日常喜欢CLion进行C&#x2F;C++开发，于是准备使用cmake重新组织该书籍的样例项目：2DGraphics1中的NimotsuKunBox和drawPixels。当然，这个过程不仅是移植，也是对cmake组织项目一个深入的实践。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>从零搭建基于react与ts的组件库（一）项目搭建与封装antd组件</title>
        <published>2022-05-27T00:00:00+00:00</published>
        <updated>2022-05-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从零搭建基于react与ts的组件库（一）项目搭建与封装antd组件/"/>
        <id>https://zhen.wang/article/从零搭建基于react与ts的组件库（一）项目搭建与封装antd组件/</id>
        
        <summary type="html">&lt;p&gt;为什么会有这样一篇文章？因为网上的教程&#x2F;示例只说了怎么做，没有系统详细的介绍引入这些依赖、为什么要这样配置，甚至有些文章还是错的！迫于技术洁癖，我希望更多的开发小伙伴能够真正的理解一个项目搭建各个方面的细节，做到面对对于工程出现的错误能够做到有把握。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>阿里低代码引擎物料开发windows下路径问题临时处理</title>
        <published>2022-04-22T00:00:00+00:00</published>
        <updated>2022-04-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/阿里低代码引擎物料开发windows下适配/"/>
        <id>https://zhen.wang/article/阿里低代码引擎物料开发windows下适配/</id>
        
        <summary type="html">&lt;p&gt;阿里低代码引擎物料开发windows下路径问题临时处理&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>画布就是一切（二） — 实现元素拖拉拽</title>
        <published>2021-11-22T00:00:00+00:00</published>
        <updated>2021-11-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/画布就是一切（二） — 实现元素拖拉拽/"/>
        <id>https://zhen.wang/article/画布就是一切（二） — 实现元素拖拉拽/</id>
        
        <summary type="html">&lt;p&gt;在《画布就是一切（一） — 基础入门》中，我们介绍了利用画布进行UI编程的基本模式，分析了如何实现鼠标悬浮在元素上，元素变色的功能。在本文中，我们依然利用画布编程的基本模式进行编程，但这一次我们将会提升一定的难度，实现元素拖拉拽的效果。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>画布就是一切（一）— 画布编程的基本模式</title>
        <published>2021-11-11T00:00:00+00:00</published>
        <updated>2021-11-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/画布就是一切（一）— 画布编程的基本模式/"/>
        <id>https://zhen.wang/article/画布就是一切（一）— 画布编程的基本模式/</id>
        
        <summary type="html">&lt;p&gt;画布编程的基本模式&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>IDEA Web渲染插件开发（二）— 自定义JsDialog</title>
        <published>2021-10-06T00:00:00+00:00</published>
        <updated>2021-10-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/IDEA Web渲染插件开发（二）— 自定义JsDialog/"/>
        <id>https://zhen.wang/article/IDEA Web渲染插件开发（二）— 自定义JsDialog/</id>
        
        <summary type="html">&lt;p&gt;《IDEA Web渲染插件开发（一）》中，我们了解到了如何编写一款用于显示网页的插件，所需要的核心知识点就是&lt;strong&gt;IDEA插件开发&lt;&#x2F;strong&gt;和&lt;strong&gt;JCEF&lt;&#x2F;strong&gt;，在本文中，我们将继续插件的开发，为该插件的JS Dialog显示进行自定义处理。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>程序员微机课系列—我的nodejs多版本管理方法</title>
        <published>2021-08-18T00:00:00+00:00</published>
        <updated>2021-08-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/程序员微机课系列-我的nodejs环境配置与使用/"/>
        <id>https://zhen.wang/article/程序员微机课系列-我的nodejs环境配置与使用/</id>
        
        <summary type="html">&lt;p&gt;nodejs的多版本配置对于我来说一直都是一个较为头疼的事情。本人的开发工作会涉及electron以及前端，对于工作中使用的npm包（点名node-sqlite3和node-sass）在某些情况下，会使用node-gyp进行原生C&#x2F;C++模块的编译，此时，nodejs的版本就尤为重要，但是本人又不太愿意使用nvm进行管理，所以总结了一套nodejs多版本管理的指南。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>极简SpringBoot指南-Chapter00-学习SpringBoot前的基本知识</title>
        <published>2021-08-10T00:00:00+00:00</published>
        <updated>2021-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/极简SpringBoot指南-chapter00-学习SpringBoot前的基本知识/"/>
        <id>https://zhen.wang/article/极简SpringBoot指南-chapter00-学习SpringBoot前的基本知识/</id>
        
        <summary type="html">&lt;h2 id=&quot;cang-ku-di-zhi&quot;&gt;仓库地址&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;springboot-simple-guide&quot;&gt;w4ngzhen&#x2F;springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter00-xue-xi-springbootqian-de-ji-ben-zhi-shi&quot;&gt;Chapter00 学习SpringBoot前的基本知识&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>极简SpringBoot指南-Chapter01-如何用Spring框架声明Bean</title>
        <published>2021-08-10T00:00:00+00:00</published>
        <updated>2021-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/极简SpringBoot指南-chapter01-如何用Spring框架声明Bean/"/>
        <id>https://zhen.wang/article/极简SpringBoot指南-chapter01-如何用Spring框架声明Bean/</id>
        
        <summary type="html">&lt;h2 id=&quot;cang-ku-di-zhi&quot;&gt;仓库地址&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;springboot-simple-guide&quot;&gt;w4ngzhen&#x2F;springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter01-ru-he-yong-springkuang-jia-sheng-ming-bean&quot;&gt;Chapter01-如何用Spring框架声明Bean&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>极简SpringBoot指南-Chapter02-Spring依赖注入的方式</title>
        <published>2021-08-10T00:00:00+00:00</published>
        <updated>2021-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/极简SpringBoot指南-chapter02-Spring依赖注入的方式/"/>
        <id>https://zhen.wang/article/极简SpringBoot指南-chapter02-Spring依赖注入的方式/</id>
        
        <summary type="html">&lt;h2 id=&quot;cang-ku-di-zhi&quot;&gt;仓库地址&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;springboot-simple-guide&quot;&gt;w4ngzhen&#x2F;springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter02-springyi-lai-zhu-ru-de-fang-shi&quot;&gt;Chapter02-Spring依赖注入的方式&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>极简SpringBoot指南-Chapter03-基于SpringBoot的Web服务</title>
        <published>2021-08-10T00:00:00+00:00</published>
        <updated>2021-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/极简SpringBoot指南-chapter03-基于SpringBoot的Web服务/"/>
        <id>https://zhen.wang/article/极简SpringBoot指南-chapter03-基于SpringBoot的Web服务/</id>
        
        <summary type="html">&lt;h2 id=&quot;cang-ku-di-zhi&quot;&gt;仓库地址&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;springboot-simple-guide&quot;&gt;w4ngzhen&#x2F;springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter03-ji-yu-springbootde-webfu-wu&quot;&gt;Chapter03-基于SpringBoot的Web服务&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>极简SpringBoot指南-Chapter04-基于SpringBoot的书籍管理Web服务</title>
        <published>2021-08-10T00:00:00+00:00</published>
        <updated>2021-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/极简SpringBoot指南-chapter04-基于SpringBoot的书籍管理Web服务/"/>
        <id>https://zhen.wang/article/极简SpringBoot指南-chapter04-基于SpringBoot的书籍管理Web服务/</id>
        
        <summary type="html">&lt;h2 id=&quot;cang-ku-di-zhi&quot;&gt;仓库地址&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;springboot-simple-guide&quot;&gt;w4ngzhen&#x2F;springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter04-ji-yu-springbootde-shu-ji-guan-li-webfu-wu&quot;&gt;Chapter04-基于SpringBoot的书籍管理Web服务&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>极简SpringBoot指南-Chapter05-SpringBoot中的AOP面向切面编程简介</title>
        <published>2021-08-10T00:00:00+00:00</published>
        <updated>2021-08-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/极简SpringBoot指南-chapter05-SpringBoot中的AOP面向切面编程简介/"/>
        <id>https://zhen.wang/article/极简SpringBoot指南-chapter05-SpringBoot中的AOP面向切面编程简介/</id>
        
        <summary type="html">&lt;h2 id=&quot;cang-ku-di-zhi&quot;&gt;仓库地址&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;w4ngzhen&#x2F;springboot-simple-guide&quot;&gt;w4ngzhen&#x2F;springboot-simple-guide: This is a project that guides SpringBoot users to get started quickly through a series of examples (github.com)&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;chapter05-springbootzhong-de-aopmian-xiang-qie-mian-bian-cheng-jian-jie&quot;&gt;Chapter05-SpringBoot中的AOP面向切面编程简介&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>IDEA Web渲染插件开发（一）— 使用JCEF</title>
        <published>2021-07-16T00:00:00+00:00</published>
        <updated>2021-07-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/IDEA Web渲染插件开发（一）— 使用JCEF/"/>
        <id>https://zhen.wang/article/IDEA Web渲染插件开发（一）— 使用JCEF/</id>
        
        <summary type="html">&lt;p&gt;目前网上已经有了很多关于IDEA（IntelliJ平台）的插件开发教程了，本人觉得简书上这位作者&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.jianshu.com&#x2F;u&#x2F;3adf83c26b4e&quot;&gt;秋水畏寒 &lt;&#x2F;a&gt;的关于插件开发的文章很不错，在我进行插件开发的过程中指导了我很多。但是综合下来看，在IDEA上加载网页的插件的教程还不是特别多，官方文档也不是那么的完整。本系列将会从这个角度出发，探讨如何编写加载Web页面的插件。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（四）— 在QT中集成CEF（1）基本集成</title>
        <published>2021-07-04T00:00:00+00:00</published>
        <updated>2021-07-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（四）— 在QT中集成CEF（1）基本集成/"/>
        <id>https://zhen.wang/article/使用CEF（四）— 在QT中集成CEF（1）基本集成/</id>
        
        <summary type="html">&lt;p&gt;QT作为C++下著名的跨平台软件开发框架，实现了一套代码可以在所有的操作系统、平台和屏幕类型上部署。我们前几篇文章讲解了如何构建一款基于CEF的简单的样例，但这些样例的GUI都是使用的原生的或者是控件功能不强大的CEF视图框架。本文将会重新开始，使用VS2019编写一款基于QT的并嵌入原生窗体的文章。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>node-gyp项目命名BUG</title>
        <published>2021-06-25T00:00:00+00:00</published>
        <updated>2021-06-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/node-gyp项目命名BUG/"/>
        <id>https://zhen.wang/article/node-gyp项目命名BUG/</id>
        
        <summary type="html">&lt;p&gt;当我们编写node原生模块的时候，免不了对node-gyp项目进行命名，在node-gyp进行build的时候，会跟binding.gyp配置文件中的target_name生成对应的原生模块。但是，如果target_name填写不规范，会触发编译问题。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用node-gyp编写简单的node原生模块</title>
        <published>2021-06-25T00:00:00+00:00</published>
        <updated>2021-06-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/编写一个简单的node原生模块/"/>
        <id>https://zhen.wang/article/编写一个简单的node原生模块/</id>
        
        <summary type="html">&lt;p&gt;通过样例，让我们了解如何编写一个node的原生模块。当然，这篇文章还有一个目的，是为了方便以后编写关于node-gyp的文章，搭建初始环境。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>从源码分析node-gyp指定node库文件下载地址</title>
        <published>2021-05-12T00:00:00+00:00</published>
        <updated>2021-05-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从源码分析node-gyp指定node库文件下载地址/"/>
        <id>https://zhen.wang/article/从源码分析node-gyp指定node库文件下载地址/</id>
        
        <summary type="html">&lt;p&gt;当我们安装node的C&#x2F;C++原生模块时，涉及到使用node-gyp对C&#x2F;C++原生模块的编译工作（configure、build）。这个过程，需要nodejs的&lt;strong&gt;头文件&lt;&#x2F;strong&gt;以及&lt;strong&gt;静态库&lt;&#x2F;strong&gt;参与（后续称库文件）对C&#x2F;C++项目编译和链接。库文件从哪里下载，会有一定逻辑进行处理，本文将从源码入手进行分析。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>electron-builder进行DEBUG输出的正确方式</title>
        <published>2021-04-18T00:00:00+00:00</published>
        <updated>2021-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/electron-builder进行DEBUG输出的正确方式/"/>
        <id>https://zhen.wang/article/electron-builder进行DEBUG输出的正确方式/</id>
        
        <summary type="html">&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;使用Electron进行打包通常会用到electron-builder或者electron-packager两种工具。在使用electron-builder的时候，由于对机制的不熟悉，我们在打包过程中常常遇到很多环境错误，但最终只是一些简单的错误信息，难以排查问题。本文将介绍electron-builder进行DEBUG输出的正确方式来帮助排查打包过程中的各种问题。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（三）— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象</title>
        <published>2021-03-31T00:00:00+00:00</published>
        <updated>2021-03-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（三）— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象/"/>
        <id>https://zhen.wang/article/使用CEF（三）— 从CEF官方Demo源码入手解析CEF架构与CefApp、CefClient对象/</id>
        
        <summary type="html">&lt;p&gt;在上文《使用CEF（2）— 基于VS2019编写一个简单CEF样例》中，我们介绍了如何编写一个CEF的样例，在文章中提供了一些代码清单，在这些代码清单中提到了一些CEF的定义的类，例如&lt;code&gt;CefApp&lt;&#x2F;code&gt;、&lt;code&gt;CefClient&lt;&#x2F;code&gt;等等。它们具体有什么作用，和CEF的进程架构有什么关系呢？本文将逐一进行介绍。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Windows下node-gyp查找VS安装路径简单解析</title>
        <published>2021-03-27T00:00:00+00:00</published>
        <updated>2021-03-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Windows下node-gyp查找VS安装路径简单解析/"/>
        <id>https://zhen.wang/article/Windows下node-gyp查找VS安装路径简单解析/</id>
        
        <summary type="html">&lt;p&gt;node-gyp的作用我已经不想赘述了，这里给一个我之前文章的链接：&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;zhuanlan.zhihu.com&#x2F;p&#x2F;330468774&quot;&gt;知乎看这里&lt;&#x2F;a&gt;。本文主要从源码入手，介绍node-gyp查找VisualStudio的过程&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Windows下的程序及热键监视神器——Spyxx</title>
        <published>2021-03-09T00:00:00+00:00</published>
        <updated>2021-03-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Windows下的程序及热键监视神器——Spyxx/"/>
        <id>https://zhen.wang/article/Windows下的程序及热键监视神器——Spyxx/</id>
        
        <summary type="html">&lt;h1 id=&quot;windowsxia-de-cheng-xu-ji-re-jian-jian-shi-shen-qi-spy&quot;&gt;Windows下的程序及热键监视神器——Spy++&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;bei-jing&quot;&gt;背景&lt;&#x2F;h2&gt;
&lt;p&gt;在使用Windows的时候，偶尔会发现某些应用程序的热键不生效了；又或是桌面弹出了弹框却并不知道这个弹框来自何处。例如，本人最近使用Vim的时候，发现创建分屏后，无法使用&lt;code&gt;ctrl+w&lt;&#x2F;code&gt;快捷键完成切屏操作，一开始以为是Vim配置出现了问题，后来发现就连Edge浏览器的&lt;code&gt;ctrl+w&lt;&#x2F;code&gt;关闭页面都无法完成，仔细一想才觉得是热键被占用了，这时候就要祭出Windows下一款简单的神器Spy++。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>iframe、SameSite与CEF</title>
        <published>2021-03-08T00:00:00+00:00</published>
        <updated>2021-03-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/iframe、SameSite与CEF/"/>
        <id>https://zhen.wang/article/iframe、SameSite与CEF/</id>
        
        <summary type="html">&lt;h1 id=&quot;iframe-samesiteyu-cef&quot;&gt;iframe、SameSite与CEF&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;bei-jing&quot;&gt;背景&lt;&#x2F;h2&gt;
&lt;p&gt;本人使用CEF（或是Chrome）来加载开发的前端页面，其中使用iframe嵌入了第三方页面，在第三方页面中需要发送cookie到后端，然而加载会报错，第三方页面后端无法接受到Cookie。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux下Electron loadURL报错 ERR_FAILED Not allowed to load local resource</title>
        <published>2021-02-24T00:00:00+00:00</published>
        <updated>2021-02-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Linux下Electron loadURL报错 ERR_FAILED Not allowed to load local resource/"/>
        <id>https://zhen.wang/article/Linux下Electron loadURL报错 ERR_FAILED Not allowed to load local resource/</id>
        
        <summary type="html">&lt;h1 id=&quot;linuxxia-electron-loadurlbao-cuo-err-failed-2-not-allowed-to-load-local-resource&quot;&gt;Linux下Electron loadURL报错 ERR_FAILED(-2) Not allowed to load local resource&lt;&#x2F;h1&gt;
&lt;p&gt;Linux Electron打包后页面无法加载，报错：Not allowed to load local resource&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>从源码解析Electron的安装为什么这么慢</title>
        <published>2021-02-01T00:00:00+00:00</published>
        <updated>2021-02-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/从源码解析Electron的安装为什么这么慢/"/>
        <id>https://zhen.wang/article/从源码解析Electron的安装为什么这么慢/</id>
        
        <summary type="html">&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;Electron作为一款跨平台的桌面应用端解决方案已经风靡全球。作为开发者，我们几乎不用关心与操作系统的交互，直接通过Web前端技术与Electron提供的API就可以完成桌面应用端的开发。&lt;&#x2F;p&gt;
&lt;p&gt;然而，为什么国内使用Electron的踩坑文章数不胜数，主要原因是Electron为了支持跨平台，为不同的操作系统平台进行了适配，将chromium内核与node集成到了一起，屏蔽了底层操作系统的细节，所以在不同的平台上有着不同的二进制基座。在开发的过程中，我们必须要下载对应的平台的基座，才能正常开发。也就是说，我们&lt;code&gt;npm install electron -D&lt;&#x2F;code&gt;的时候，一定是下载了Electron的二进制基座的。那么这个下载的过程在哪里？为什么速度这么慢呢？本文将通过Electron的安装源码一一说明。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（二）— 基于VS2019编写一个简单CEF样例</title>
        <published>2021-01-15T00:00:00+00:00</published>
        <updated>2021-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（二）— 基于VS2019编写一个简单CEF样例/"/>
        <id>https://zhen.wang/article/使用CEF（二）— 基于VS2019编写一个简单CEF样例/</id>
        
        <summary type="html">&lt;h1 id=&quot;shi-yong-cef-er-ji-yu-vs2019bian-xie-yi-ge-jian-dan-cefyang-li&quot;&gt;使用CEF（二）— 基于VS2019编写一个简单CEF样例&lt;&#x2F;h1&gt;
&lt;p&gt;在这一节中，本人将会在Windows下使用VS2019创建一个空白的C++&lt;strong&gt;Windows Desktop Application&lt;&#x2F;strong&gt;项目，逐步进行修改配置和代码编写，并在这个过程中介绍vs使用过程中和C++项目的结合。源码见文章末尾Github链接。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>使用CEF（一）— 起步</title>
        <published>2021-01-12T00:00:00+00:00</published>
        <updated>2021-01-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/使用CEF（一）— 起步/"/>
        <id>https://zhen.wang/article/使用CEF（一）— 起步/</id>
        
        <summary type="html">&lt;h1 id=&quot;shi-yong-cef-yi-qi-bu&quot;&gt;使用CEF（一）— 起步&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;jie-shao&quot;&gt;介绍&lt;&#x2F;h2&gt;
&lt;p&gt;Chromium Embedded Framework (CEF)是个基于Google Chromium项目的开源Web browser控件，支持Windows, Linux, Mac平台。除了提供C&#x2F;C++接口外，也有其他语言的移植版。&lt;&#x2F;p&gt;
&lt;p&gt;因为基于Chromium，所以CEF支持&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;baike.baidu.com&#x2F;item&#x2F;Webkit&quot;&gt;Webkit&lt;&#x2F;a&gt; &amp;amp; Chrome中实现的&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;baike.baidu.com&#x2F;item&#x2F;HTML5&quot;&gt;HTML5&lt;&#x2F;a&gt;的特性，并且在性能上面，也比较接近Chrome。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>node-pre-gyp以及node-gyp的源码简单解析（以安装sqlite3为例）</title>
        <published>2020-11-27T00:00:00+00:00</published>
        <updated>2020-11-27T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/node-pre-gyp以及node-gyp的源码简单解析（以安装sqlite3为例）/"/>
        <id>https://zhen.wang/article/node-pre-gyp以及node-gyp的源码简单解析（以安装sqlite3为例）/</id>
        
        <summary type="html">&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;简单来说，node是跨平台的，那么对于任何的node模块理论也是应该是跨平台的。然而，有些node模块直接或间接使用原生C&#x2F;C++代码，这些东西要跨平台，就需要使用源码根据实际的操作平台环境进行原生模块编译。SQLite3就是一个经典的原生模块，让我们以安装该模块为例，探索一下安装原生模块的流程。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>再议Windows消息与WinForm事件</title>
        <published>2020-10-13T00:00:00+00:00</published>
        <updated>2020-10-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/再议Windows消息与WinForm事件/"/>
        <id>https://zhen.wang/article/再议Windows消息与WinForm事件/</id>
        
        <summary type="html">&lt;h1 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h1&gt;
&lt;p&gt;在2月份的时候，我之前曾经写过一篇关于Windows消息与C# WinForm事件机制的文章，名为《WinForm事件与消息》。在那篇文章中，我简单探讨了一下事件和消息。然而如今看来，当时的文章中的案例在运行上存在一定的问题，并且内容也有所缺陷，于是本文将重新优化文章的内容。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>VirtualBox上安装Debian10</title>
        <published>2020-09-29T00:00:00+00:00</published>
        <updated>2020-09-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/VirtualBox上安装Debian10/"/>
        <id>https://zhen.wang/article/VirtualBox上安装Debian10/</id>
        
        <summary type="html">&lt;p&gt;本文将介绍如何使用VBox进行Debian10的安装&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>CefSharp请求资源拦截及自定义处理</title>
        <published>2020-08-23T00:00:00+00:00</published>
        <updated>2020-08-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/CefSharp资源拦截处理/"/>
        <id>https://zhen.wang/article/CefSharp资源拦截处理/</id>
        
        <summary type="html">&lt;h1 id=&quot;cefsharpqing-qiu-zi-yuan-lan-jie-ji-zi-ding-yi-chu-li&quot;&gt;CefSharp请求资源拦截及自定义处理&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h2&gt;
&lt;p&gt;在CefSharp中，我们不仅可以使用Chromium浏览器内核，还可以通过Cef暴露出来的各种Handler来实现我们自己的资源请求处理。&lt;&#x2F;p&gt;
&lt;p&gt;什么是资源请求呢？简单来说，就是前端页面在加载的过程中，请求的各种文本（js、css以及html）。在以Chromium内核的浏览器上，我们可以使用浏览器为我们提供的开发者工具来检查每一次页面加载发生的请求。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>S_型文法到q_型文法再到LL(1)型文法演进笔记</title>
        <published>2020-08-23T00:00:00+00:00</published>
        <updated>2020-08-23T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/S_型文法到q_型文法再到LL(1)型文法演进笔记/"/>
        <id>https://zhen.wang/article/S_型文法到q_型文法再到LL(1)型文法演进笔记/</id>
        
        <summary type="html">&lt;h1 id=&quot;s-xing-wen-fa-dao-q-xing-wen-fa-zai-dao-ll-1-xing-wen-fa-yan-jin-bi-ji&quot;&gt;S_型文法到q_型文法再到LL(1)型文法演进笔记&lt;&#x2F;h1&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Electron加Vue加ElementUI开发环境搭建</title>
        <published>2020-08-08T00:00:00+00:00</published>
        <updated>2020-08-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Electron加Vue加ElementUI开发环境搭建/"/>
        <id>https://zhen.wang/article/Electron加Vue加ElementUI开发环境搭建/</id>
        
        <summary type="html">&lt;p&gt;本文将从零开始，进行Electron+Vue+ElementUI的开发环境搭建&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>CefSharp基于.Net Framework 4.0 框架编译</title>
        <published>2020-06-06T00:00:00+00:00</published>
        <updated>2020-06-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/CefSharp基于.Net Framework 4.0 框架编译/"/>
        <id>https://zhen.wang/article/CefSharp基于.Net Framework 4.0 框架编译/</id>
        
        <summary type="html">&lt;p&gt;本次源码使用的是Github上CefSharp官方的79版本源码&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>WinForm事件与消息</title>
        <published>2020-02-05T00:00:00+00:00</published>
        <updated>2020-02-05T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/WinForm事件与消息/"/>
        <id>https://zhen.wang/article/WinForm事件与消息/</id>
        
        <summary type="html">&lt;h3 id=&quot;xiao-xi-gai-shu-yi-ji-zai-c-xia-de-feng-zhuang&quot;&gt;消息概述以及在C#下的封装&lt;&#x2F;h3&gt;
&lt;p&gt;Windows下应用程序的执行是通过消息驱动的。所有的外部事件，如键盘输入、鼠标移动、按动鼠标都由OS系统转换成相应的“消息”，进入到应用程序的消息队列中，由应用程序引擎轮询处理。在C#中，消息被应用程序的工作引擎通过轮询等方式遍历获取并按照消息的类型逐个分发到对应的组件（例如窗体、按钮等），最后调用对应组件所注册的事件进行处理。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>基于Tesseract组件的OCR识别</title>
        <published>2020-02-04T00:00:00+00:00</published>
        <updated>2020-02-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/基于Tesseract组件的OCR识别学习/"/>
        <id>https://zhen.wang/article/基于Tesseract组件的OCR识别学习/</id>
        
        <summary type="html">&lt;h2 id=&quot;bei-jing-yi-ji-jie-shao&quot;&gt;背景以及介绍&lt;&#x2F;h2&gt;
&lt;p&gt;欲研究C#端如何进行图像的基本OCR识别，找到一款开源的OCR识别组件。该组件当前已经已经升级到了4.0版本。和传统的版本（3.x）比，4.0时代最突出的变化就是基于LSTM神经网络。Tesseract本身是由C++进行编写，但为了同时适配不同的语言进行调用，开放调用API并产生了诸如Java、C#、Python等主流语言在内的封装版本。本次主要研究C#封装版。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>那些我用Windows时必备的软件</title>
        <published>2019-02-25T00:00:00+00:00</published>
        <updated>2019-02-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/那些我用Windows时必备的软件/"/>
        <id>https://zhen.wang/article/那些我用Windows时必备的软件/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/那些我用Windows时必备的软件/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h1 id=&quot;jian-guo-yun&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.jianguoyun.com&#x2F;s&#x2F;downloads&quot;&gt;坚果云&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;microsoft-yahei-mono-zi-ti-ti-qu-ma-epsq&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pan.baidu.com&#x2F;s&#x2F;1WuCJ5VW7nypzp4dgCTcMtQ&quot;&gt;Microsoft YaHei Mono 字体&lt;&#x2F;a&gt; （提取码：epsq）&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;7-zip&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.7-zip.org&#x2F;&quot;&gt;7-Zip&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;aida64extreme&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.aida64.com&#x2F;downloads&quot;&gt;AIDA64Extreme&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;key：FARRD-CU2D6-J9D59-LD2Q4-3AJG7
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;apache-maven&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;maven.apache.org&#x2F;download.cgi&quot;&gt;apache-maven&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;ol&gt;
&lt;li&gt;环境变量设置&lt;&#x2F;li&gt;
&lt;li&gt;阿里镜像配置&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;xml&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-xml &quot;&gt;&lt;code class=&quot;language-xml&quot; data-lang=&quot;xml&quot;&gt;&lt;span&gt;&#x2F;&#x2F; bin目录conf中复制一份settings.xml到用户目录&#x2F;.m2&#x2F;下（没有就手动创建）
&lt;&#x2F;span&gt;&lt;span&gt;&#x2F;&#x2F; 在settings.xml的mirrors节点下面添加如下子节点
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mirror&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;	&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;nexus-aliyun&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;id&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;	&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mirrorOf&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;central&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mirrorOf&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;	&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;Nexus aliyun&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;name&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;	&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;http:&#x2F;&#x2F;maven.aliyun.com&#x2F;nexus&#x2F;content&#x2F;groups&#x2F;public&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;url&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;mirror&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;命令行进行初始化&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;mvn help:system
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;baidunetdisk&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pan.baidu.com&#x2F;download&quot;&gt;BaiduNetdisk&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;vlc-media-player&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.videolan.org&#x2F;&quot;&gt;VLC media player&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;di-da-qing-dan&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.dida365.com&#x2F;about&#x2F;download&quot;&gt;滴答清单&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;eudic&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.eudic.net&#x2F;v4&#x2F;en&#x2F;app&#x2F;eudic&quot;&gt;eudic&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;everything&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.voidtools.com&#x2F;zh-cn&#x2F;&quot;&gt;Everything&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;fscapture&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;faststone.org&#x2F;FSCaptureDownload.htm&quot;&gt;FSCapture&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;name：bluman
&lt;&#x2F;span&gt;&lt;span&gt;key：VPISCJULXUFGDDXYAUYF
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h1 id=&quot;git&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;git-scm.com&#x2F;downloads&quot;&gt;Git&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;grepwin&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;stefankueng&#x2F;grepWin&#x2F;releases&quot;&gt;grepWin&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;ilspy&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;icsharpcode&#x2F;ILSpy&#x2F;releases&quot;&gt;ILSpy&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;jdk8-adoptopenjdk&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;adoptopenjdk.net&#x2F;releases.html?variant=openjdk8&amp;amp;jvmVariant=hotspot&quot;&gt;jdk8（AdoptOpenJDK）&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;ol&gt;
&lt;li&gt;环境变量设置&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;h1 id=&quot;jetbrains-intellij-idea&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.jetbrains.com&#x2F;idea&#x2F;download&#x2F;&quot;&gt;JetBrains IntelliJ IDEA&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;microsoft-visual-studio&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;visualstudio.microsoft.com&#x2F;zh-hans&#x2F;vs&#x2F;&quot;&gt;Microsoft Visual Studio&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;nodejs&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nodejs.org&#x2F;en&#x2F;&quot;&gt;nodejs&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;vim&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.vim.org&#x2F;&quot;&gt;vim&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;picgo&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Molunerfinn&#x2F;PicGo&#x2F;releases&quot;&gt;PicGo&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;r&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;shadowsocks&#x2F;shadowsocks-windows&#x2F;releases&quot;&gt;$$R&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;sumatrapdf&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;sumatrapdfreader&#x2F;sumatrapdf&#x2F;releases&quot;&gt;SumatraPDF&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;thunder-network-ti-qu-ma-ufce&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;pan.baidu.com&#x2F;s&#x2F;1b8mJY_wwGO8zt0nCDVW86Q&quot;&gt;Thunder Network&lt;&#x2F;a&gt; (提取码：ufce)&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;typora&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;typora.io&#x2F;&quot;&gt;Typora&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;utools&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;u.tools&#x2F;&quot;&gt;uTools&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;autohotkey&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.autohotkey.com&#x2F;&quot;&gt;AutoHotKey&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h1 id=&quot;winscp&quot;&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;winscp.net&#x2F;eng&#x2F;download.php&quot;&gt;WinSCP&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>关于人的思考</title>
        <published>2019-02-16T00:00:00+00:00</published>
        <updated>2019-02-16T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/关于人的思考/"/>
        <id>https://zhen.wang/article/关于人的思考/</id>
        
        <summary type="html">&lt;p&gt;&lt;em&gt;人总是要思考的。人生一世，思考总是每时每刻都伴随我们前行。各种情景中，如：失落、高兴、烦恼等等，我们都要思考。有了思考，我们才能在这个世界立足;有了思考，我们才能与众不同，拥有自己的标志。思考是无形的，但它却在无形中左右着我们的生活，左右着我们的人生之路。“思考”是一件很神奇的东西，因为它，我们才能够解决问题;因为它，我们才能讨论“关于人的思考”。我们的世界正是因为有很多善于思考的人才会如此的进步，也正是因为有那些不善于思考的人才会变的如此复杂。&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>硝烟中的Scrum和XP</title>
        <published>2019-01-15T00:00:00+00:00</published>
        <updated>2019-01-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/硝烟中的Scrum和XP/"/>
        <id>https://zhen.wang/article/硝烟中的Scrum和XP/</id>
        
        <summary type="html">&lt;p&gt;初次接触Scrum和XP（更加准确的说是“看到”），心里不免有些疑问，软件开发为什么会有如此多的方式，难道软件开发、软件工程不就是写写代码的事儿吗？直到后来，才明白，一个庞大的软件工程，不会只是一个人的事儿，倘若我们现在（学生时代）还是只有着一种写代码是自己的事儿的态度来看待软件工程这样的“工程”，是低端的，是不全面的。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>人月神话</title>
        <published>2019-01-01T00:00:00+00:00</published>
        <updated>2019-01-01T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/人月神话/"/>
        <id>https://zhen.wang/article/人月神话/</id>
        
        <summary type="html">&lt;p&gt;先来讲一讲“睡前”想法，也许会有所偏离主题，请见谅。初次面对“人月神话”这个标题的时候，就不禁从中感受到了一种差距，一种关于国内与国外（西方）计算机软件相关的文化差距。就最近所接触到的计算机相关知识来看，我觉文艺复兴的意义是非同凡响的，是深远的，最近接触到的许多关于计算机、软件程序相关的书籍，尽管还未曾读过，但名字已经给我留下了深刻的印象。例如，《人件》、《大教堂与集市》、《黑客与画家》、《人月神话》等等。也许我监视太过于片面，但我还是想说尽管国内程序开发人员、管理人员在技术层面上与国外同行差距并不是很大，甚至在某些方面更加优秀，然而，文化层面却是差了一大截，缺少了灵性，缺少了思维的敢于跳跃。追根溯源，我认为是西方的历史、文艺复兴、文化、哲学给西方的计算机领域注入了太多有意味的，有营养的东西。正如翻开《软件工程》这本厚重的书的第一页，看到序章的第一句话“文艺复兴以降”。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>再见，2018</title>
        <published>2018-12-31T00:00:00+00:00</published>
        <updated>2018-12-31T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/再见，贰零壹捌/"/>
        <id>https://zhen.wang/article/再见，贰零壹捌/</id>
        
        <summary type="html">&lt;h4 id=&quot;zai-jian-2018&quot;&gt;再见，2018&lt;&#x2F;h4&gt;
&lt;p&gt;这一年我毕业在6月，离别在7月。似乎每个人都要等到怀念的时候，才会觉得当初的好，就像在毕业之后，我时常怀念起大学的美好时光。要说每一幕都难以忘记似乎太假惺惺，但却依然难以忘记大家的第一次的见面，第一次的聚会，第一次的活动，以及第一次也是最后一次的离别。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>演员 Or 开发者的自我修养</title>
        <published>2018-11-11T00:00:00+00:00</published>
        <updated>2018-11-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/演员 Or 开发者的自我修养/"/>
        <id>https://zhen.wang/article/演员 Or 开发者的自我修养/</id>
        
        <summary type="html">&lt;p&gt;时至今日，我都还是很怀念小时候与一群玩伴编写剧本、拍摄，那时候的我还有一个远大的“白日梦”——成为一名导演。很可惜，终究是“白日梦”。在完成了一系列的“艰苦”拍摄以后，一个半成品的微电影就出世了：没有字幕，没有主题，昏暗的镜头，富有浓郁特色的四川方言。随后发布到网上，差评如潮：）。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>九零后</title>
        <published>2018-10-25T00:00:00+00:00</published>
        <updated>2018-10-25T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/九零后/"/>
        <id>https://zhen.wang/article/九零后/</id>
        
        <summary type="html">&lt;p&gt;“70后”是理想，“80后”是仗义，那么“90后”是什么？&lt;&#x2F;p&gt;
&lt;p&gt;2019年，最小的“90后”应该已经步入了大学校园，正找寻着自己的理想；最大的“90后”应该有的已经成家立业，担起了另一种角色。或许他们曾失落过、迷茫过，但他们终将一一突破。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>CSharpEntityFramework与CodeFirst实践</title>
        <published>2018-09-13T00:00:00+00:00</published>
        <updated>2018-09-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/CSharpEntityFramework与CodeFirst实践/"/>
        <id>https://zhen.wang/article/CSharpEntityFramework与CodeFirst实践/</id>
        
        <summary type="html">&lt;h3 id=&quot;qian-yan&quot;&gt;前言&lt;&#x2F;h3&gt;
&lt;p&gt;当我们进行开发的时候,常常会用到数据库来对数据进行持久化的操作，有的时候,我们并不想要在进行代码开发的过程中，还去关注数据库的构建,表的构建等等。于是，就有了Code First模式。何为Code First模式呢？它思想就是先定义模型中的类，再通过这些类生成数据库。这种开发模式适合于全新的项目，它使得我们可以以代码为核心进行设计而不是先构造数据库。这样一来，使得我们更加关注代码的开发。在c#中，我们使用EntityFramework来实现Code First场景。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>WebXml文件与SpringMVC的联系</title>
        <published>2018-08-26T00:00:00+00:00</published>
        <updated>2018-08-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/WebXml文件与SpringMVC的联系/"/>
        <id>https://zhen.wang/article/WebXml文件与SpringMVC的联系/</id>
        
        <summary type="html">&lt;p&gt;无论采用何种框架来进行Java Web的开发，只要是Web项目必须在WEB-INF下有web.xml，这是java规范。 当然，我们最早接触到Java Web容器通常是tomcat，但这并不意味着web.xml是属于Tomcat的，同样，Servlet本身也不属于Tomcat，它与JSP等是Java Web的基础规范。而Servlet的运行需要有Servlet容器的支持，常见的容器有Tomcat、Jetty、JBoss等。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>CSharp委托与匿名函数</title>
        <published>2018-08-04T00:00:00+00:00</published>
        <updated>2018-08-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/CSharp委托与匿名函数/"/>
        <id>https://zhen.wang/article/CSharp委托与匿名函数/</id>
        
        <summary type="html">&lt;h4 id=&quot;chang-jing&quot;&gt;场景&lt;&#x2F;h4&gt;
&lt;p&gt;面对事件处理，我们通常会通过定义某一个通用接口，在该接口中定义方法，然后在框架代码中，调用实现该接口的类实例的方法来实现函数的回调。可能这样来说有些抽象，那我们提供一个具体的情形来实现这一情形。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>毕做你所做业所想</title>
        <published>2018-07-29T00:00:00+00:00</published>
        <updated>2018-07-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/做你所做/"/>
        <id>https://zhen.wang/article/做你所做/</id>
        
        <summary type="html">&lt;h4 id=&quot;zuo-ye&quot;&gt;昨夜&lt;&#x2F;h4&gt;
&lt;p&gt;昨晚我失眠了，因为在自己的机器上装东西，顺便看了小米的纪录片《一团火》。也许是太久没有看到关于非产品方面的小米了，也许是太想要看到小米一路走来的路了，看完后我内心久久不能平复。待到后半夜，我内心依然如纪录片一般“一团火”。那一群人，那样的坚定，让我羡慕与感动。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Spring配置文件结构对于生成Bean的影响</title>
        <published>2018-07-02T00:00:00+00:00</published>
        <updated>2018-07-02T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Spring配置文件结构对于生成Bean的影响/"/>
        <id>https://zhen.wang/article/Spring配置文件结构对于生成Bean的影响/</id>
        
        <summary type="html">&lt;p&gt;由于前段时间忙于毕设，导致Spring学习的东西忘了很多，所以最近又开始从头看Spring的基础。基础的Bean的装配不再多说了。这一次，主要是深入一点了解Spring配置文件结构搭配对于Bean装配的影响。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>毕业所想</title>
        <published>2018-06-17T00:00:00+00:00</published>
        <updated>2018-06-17T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/毕业所想/"/>
        <id>https://zhen.wang/article/毕业所想/</id>
        
        <summary type="html">&lt;p&gt;距离上一次博文过了接近一个月了，这期间毕设虽忙，但不至于忙到连博文都写不了的地步。早就在寻思着什么时候更一篇文章，但是就是懒，好像每天忙完毕设的事情之后，就感觉自己今天做完一件事，就可以休息了，就可以放纵自己了。其实不过都是借口罢了。趁着这个时间，写写东西，回顾一下写过的东西，觉得写的东西还是太分散了，或者说，目前为止都没有一个明确的写作目标，这里也表明一下，顺便也为今后的写作约束一下自己。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Java枚举细节</title>
        <published>2018-05-18T00:00:00+00:00</published>
        <updated>2018-05-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Java枚举细节/"/>
        <id>https://zhen.wang/article/Java枚举细节/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Java枚举细节/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;&lt;h4 id=&quot;mei-ju-de-jian-dan-shi-yong&quot;&gt;枚举的简单使用&lt;&#x2F;h4&gt;
&lt;p&gt;在java中，我们可以使用enum关键字来定义枚举：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;就像上面一样，我们定义了一个名为Color的枚举类，包含了RED、GREEN、BLUE三个常量。当我们使用枚举类的时候，直接通过枚举类名.枚举常量即可。就像如下的形式：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt; c) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;switch &lt;&#x2F;span&gt;&lt;span&gt;(c) {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;It&amp;#39;s RED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;break&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;It&amp;#39;s GREEN&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;break&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;It&amp;#39;s BLUE&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 调用
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;f&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 输出 &amp;quot;It&amp;#39;s RED&amp;quot;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;mei-ju-de-ben-zhi&quot;&gt;枚举的本质&lt;&#x2F;h4&gt;
&lt;p&gt;虽然Java提供枚举类的定义，但是实际上他并不是Java中一个新的对象类型，我们通过对Color枚举类进行反编译，得到如下的反编译结果：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;$ javap &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class 
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 反编译结果
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Compiled&lt;&#x2F;span&gt;&lt;span&gt; from &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Color.java&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public final class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt; extends java.lang.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Enum&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;[] &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;values&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;EnumerationAndAnnotation&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;valueOf&lt;&#x2F;span&gt;&lt;span&gt;(java.lang.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;static &lt;&#x2F;span&gt;&lt;span&gt;{};
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;我们可以看到，枚举类实际上在编译的过程中，被编译器进行调整，它并不是一个新的类型，本质上依然是一个类（Color），这个类继承了java.lang.Enum&lt;T&gt;，而对于每一个枚举常量，实际上是public static final修饰的枚举类的静态实例对象。&lt;&#x2F;p&gt;
&lt;p&gt;同时注意，编译器会为我们添加两个新的static方法：values() 和 valueOf(java.lang.String)，其实分别作用是返回枚举类中定义的所有的枚举常量，以及根据枚举名来获取枚举常量（注意，这里就是定义枚举常量的枚举名）。&lt;&#x2F;p&gt;
&lt;p&gt;当然，由于每一个枚举常量实际上是实现了java.lang.Enum&lt;T&gt;的枚举类的一个静态实例对象，而这个过程是编译器为我们进行的，所以，自然，我们可以在枚举类中定义任何的方法、变量，以及构造函数的定义：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 定义类中的实例变量
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;colorName;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 定义构造函数，注意上面的枚举常量必须满足这种构造方式
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;colorName&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.colorName &lt;&#x2F;span&gt;&lt;span&gt;=&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; colorName;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 定义实例对象的方法
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;printColorName&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Color name is &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; +&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; colorName);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 重写toString方法
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;toString&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; colorName;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;自然，我们可以枚举常量当作一个枚举的实例化对象，调用枚举类中的方法：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static&lt;&#x2F;span&gt;&lt;span&gt; void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span&gt; args) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; RED
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;printColorName&lt;&#x2F;span&gt;&lt;span&gt;();	&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; Color name is RED
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;wei-shen-me-mei-ju-gou-zao-qi-bu-neng-fang-wen-mei-ju-de-jing-tai-yu&quot;&gt;为什么枚举构造器不能访问枚举的静态域&lt;&#x2F;h4&gt;
&lt;p&gt;查看下面这段代码：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private static int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;value &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;Color&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(value); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 编译错误！构造器无法访问静态变量
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;我们可以看到这里编译不通过，提示我们&lt;strong&gt;枚举构造器&lt;&#x2F;strong&gt;不能够访问枚举的静态域（以及静态变量）。我们知道，一般的类中，静态域以及静态变量是优于实例对象的变量、方法的初始化的。这里简要复习一下类中变量的加载机制：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 辅助静态变量的初始化
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;Init&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;init&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Init()&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot; +&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt; init);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Fa &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 父类静态变量
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;static final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;initFa &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fa&amp;#39;s static&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 父类实例变量
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;initFa2 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fa&amp;#39;s no static&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 父类静态域
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;static &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fa&amp;#39;s static&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 父类构造方法
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;Fa&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fa()&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Su &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;extends &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Fa &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 子类静态域
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;static &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Su&amp;#39;s static&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 子类静态变量
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;static final &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;initSu &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Su&amp;#39;s static&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 子类实例变量
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;initSu2 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Init&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Su&amp;#39;s no static&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 子类构造函数
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;Su&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Su()&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;接下来，我们new出Su实例对象，并观察输出结果：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public static&lt;&#x2F;span&gt;&lt;span&gt; void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;main&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;[]&lt;&#x2F;span&gt;&lt;span&gt; args) {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Su&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;* 输出
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Init()Fa&amp;#39;s static 	&#x2F;&#x2F; 1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Fa&amp;#39;s static			&#x2F;&#x2F; 2
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Su&amp;#39;s static			&#x2F;&#x2F; 3
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Init()Su&amp;#39;s static	&#x2F;&#x2F; 4
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Init()Fa&amp;#39;s no static&#x2F;&#x2F; 5
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Fa()				&#x2F;&#x2F; 6
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Init()Su&amp;#39;s no static&#x2F;&#x2F; 7
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;Su()				&#x2F;&#x2F; 8
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;*&#x2F;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;我们可以看到，有static修饰的始终优于实例对象的相关的初始化的，在输出中 1 - 4 是static修饰部分，5 - 8是实例域部分。此外，在继承情形下，父类由于子，输出中 1- 2 是父类static域的初始化，3 - 4 是子类static域的初始化。在static域加载完成之后，才开始加载父类非static域，最后加载子类的非static域。注意，都为static修饰的情况下，加载顺序根绝定义时候的顺序而来，1、2与3、4就可以看出。&lt;&#x2F;p&gt;
&lt;p&gt;看到这里，也许会有点疑问，既然静态域加载优于实例域（包含构造函数），那为什么在枚举类中就不行呢？让我们回到前面对枚举类的反编译，其实答案就出来了。反编译的过程我们可以看到，我们的枚举常量实际上是我们枚举类的静态实例化对象，在编译器的修改下，我们运行加载枚举类的过程中，枚举常量是static修饰的，其他静态域也是static修饰的，枚举常量又排在其他静态域的前面，按照上面的额初始化顺序，首先就会调用构造器实例化枚举常量对象，此时，枚举类中的其他静态域都还没来得及初始化，自然在构造函数中不能访问静态域了。有人可能想说，那我静态域放到枚举常量前面，让他先加载怎么样？很遗憾，Java不允许这样做：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 编译不通过！！！
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;private static int &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;value &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h4 id=&quot;shi-yong-chou-xiang-han-shu-wei-mei-ju-tong-yi-fang-fa&quot;&gt;使用抽象函数为枚举统一方法&lt;&#x2F;h4&gt;
&lt;p&gt;上面的内容探讨了枚举类的一些基础，这里提一些关于使用枚举的代码策略设计。
有的时候，我们想要给枚举常量定义某一些通用的方法，同时，针对不同的枚举，该通用方法呈现不同的具体内容。例如，我现在有一个如下的Color枚举类，当针对不同的Color常量的时候，能有一个方式给我返回该颜色的十六进制颜色码。也许你会如下来实现&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;getHexCode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;switch &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;this&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;) {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#FF0000&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;case &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#00FF00&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;default&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#0000FF&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;以上的方式较为简洁与易读，但是存在问题：枚举常量越来越多，case会不断增长，如果编写代码的过程由于不注意，增加颜色常量的时候，忘记了增加对应的case，那么编译是不会有任何的问题的，但是却隐含的将增加的颜色常量也返回的是BLUE的十六进制颜色码。&lt;&#x2F;p&gt;
&lt;p&gt;鉴于上述的问题，我们需要某种方式来防止我们犯错，能够想到的，就是通过编译器来告诉我们。于是，我们在枚举类中定一个抽象方法getHexCode，于是乎，对于每一个枚举常量，编译器会提示我们实现具体实例的getHexCode：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Color &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RED &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;getHexCode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#FF0000&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;GREEN &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;getHexCode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#00FF00&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;BLUE &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;getHexCode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;#0000FF&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    };
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;abstract public &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;getHexCode&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在这样的定义下，代码牺牲了一定的简洁性，但是易读性丝毫不输于最开始的方式。针对Color枚举类，我们定义了抽象方法，表明了对于Color中的每一个枚举常量，都应该有getHexCode方法，返回自己的十六进制颜色码。如果我们新添加了枚举常量，而没有实现该方法，编译器会报错警告我们。&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Java泛型中的细节</title>
        <published>2018-05-03T00:00:00+00:00</published>
        <updated>2018-05-03T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Java泛型中的细节/"/>
        <id>https://zhen.wang/article/Java泛型中的细节/</id>
        
        <summary type="html">&lt;h3 id=&quot;ru-guo-mei-you-fan-xing&quot;&gt;如果没有泛型&lt;&#x2F;h3&gt;
&lt;p&gt;学习Java，必不可少的一个过程就是需要掌握泛型。泛型起源于JDK1.5，为什么我们要使用泛型呢？泛型可以使编译器知道一个对象的限定类型是什么，这样编译器就可以在一个高的程度上验证这个类型消除了强制类型转换，使得代码可读性好，而这个过程是发生在编译时期的，即在编译时期发现代码中类型转换的错误所在，及时发现，而不必等到运行时期抛出运行时期的类型转换异常。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Scala trait特质深入理解</title>
        <published>2018-04-24T00:00:00+00:00</published>
        <updated>2018-04-24T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Scala trait特质深入理解/"/>
        <id>https://zhen.wang/article/Scala trait特质深入理解/</id>
        
        <summary type="html">&lt;h3 id=&quot;chu-tan-scala-te-zhi-trait&quot;&gt;初探Scala 特质trait&lt;&#x2F;h3&gt;
&lt;p&gt;在Scala中，trait（特质）关键字有着举足轻重的作用。就像在Java中一样，我们只能在Scala中通过extends进行单一继承，但trait可以让我们从语义上实现了多重继承。通过对继承的类混入（mixin）多个特质，来达到多重继承的目的。乍一看，trait和Java中的interface接口很像，但是，细节上它们又有着大不同。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux下iptables学习笔记</title>
        <published>2018-04-18T00:00:00+00:00</published>
        <updated>2018-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Linux下iptables笔记/"/>
        <id>https://zhen.wang/article/Linux下iptables笔记/</id>
        
        <summary type="html">&lt;p&gt;在Centos7版本之后，防火墙应用已经由从前的iptables转变为firewall这款应用了。但是，当今绝大多数的Linux版本（特别是企业中）还是使用的6.x以下的Centos版本，所以对iptables的了解还是很有必要的。此外，需要说明的是iptables自身并不具备防火墙的功能，它需要通过内核netfilter（网络过滤器）来实现，与firewalld一样，他们的作用都是用于维护规则，而真正使用规则干活的是内核的netfilter，只不过firewalld和iptables的结构以及使用方法不一样，他们都只是一个外壳应用罢了。打个比方，就好像有一本书，同样的内容，一种是纸质的，另一种是电子的，我们翻阅它的方式不同，给它做笔记的方式不同，但是内容（内核）一样。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>树莓派3B上手一二</title>
        <published>2018-04-12T00:00:00+00:00</published>
        <updated>2018-04-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/树莓派3B上手一二/"/>
        <id>https://zhen.wang/article/树莓派3B上手一二/</id>
        
        <summary type="html">&lt;p&gt;早些时间心血来潮买过一个树莓派，但是当时只是玩一玩，买来按照网上的教程摆弄了一下就闲置了。最近毕业设计，做时序数据分析的相关的工作，刚好想起能够用到树莓派+Node-RED来生成模拟的时序数据。于是开始搭建相关的环境。特此记录一下。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>树莓派3B搭建NODE-RED运行环境并构建数据流</title>
        <published>2018-04-12T00:00:00+00:00</published>
        <updated>2018-04-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/树莓派3B搭建NODE-RED运行环境并进行基础练习/"/>
        <id>https://zhen.wang/article/树莓派3B搭建NODE-RED运行环境并进行基础练习/</id>
        
        <summary type="html">&lt;p&gt;&lt;strong&gt;树莓派搭建Node-RED环境&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;树莓派自2015年开始是默认就带NODE-RED的，但是如今已是2018年：）自带的版本已经很老了，可通过下面的命令进行自动升级NODE.JS和NODE-RED&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux文件系统与inode、Block笔记</title>
        <published>2018-04-08T00:00:00+00:00</published>
        <updated>2018-04-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Linux文件系统与inode、Block笔记/"/>
        <id>https://zhen.wang/article/Linux文件系统与inode、Block笔记/</id>
        
        <summary type="html">&lt;p&gt;在Linux下一切都是文件，无论是设备还是接口，亦或是网卡等均被抽象成了文件，并且有相关的内核代码进行调度。然而，在一切都是文件的前提下，最需要进行探讨的则是文件存储的根源：文件系统。文件系统的好坏能够更加完美的解决在一个操作系统中对于文件的管理。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Java编译运行环境讨论（复古但能加深对Java项目的理解）</title>
        <published>2018-03-30T00:00:00+00:00</published>
        <updated>2018-03-30T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Java编译运行环境讨论（复古但能加深对Java项目的理解）/"/>
        <id>https://zhen.wang/article/Java编译运行环境讨论（复古但能加深对Java项目的理解）/</id>
        
        <summary type="html">&lt;p&gt;如今我们大多数情况都会使用IDE来进行Java项目的开发，而一个如今众多优秀的IDE已经能够帮助我们自动的部署并调试运行我们的Java程序。然而在早期我们进行Java开始需要手动的建立逻辑包（package）与目录来管理我们的Java项目或是更高级一点的则是使用ant这样的构建工具。作为Javaer，对于Java的编译过程应当是熟悉的，这样即使脱离了IDE我们依然能够很好的理解Java的构建过程。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>macOS下Java与JDK关系与相关路径</title>
        <published>2018-03-15T00:00:00+00:00</published>
        <updated>2018-03-15T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/macOS下Java与JDK关系与相关路径/"/>
        <id>https://zhen.wang/article/macOS下Java与JDK关系与相关路径/</id>
        
        <summary type="html">&lt;p&gt;macOS下的Java与JDK的路径曾经困扰过我一段时间，今天稍有些忘记，故记下笔记，整理一下。Java与JDK的关系不在本文笔记之内，Javaer常识。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux Bash命令杂记(tr col join paste expand)</title>
        <published>2018-03-13T00:00:00+00:00</published>
        <updated>2018-03-13T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Linux Bash命令杂记(tr col join paste expand)/"/>
        <id>https://zhen.wang/article/Linux Bash命令杂记(tr col join paste expand)/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Linux Bash命令杂记(tr col join paste expand)/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;strong&gt;tr命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;tr命令可以将输入的数据中的某些字符做替换或者是作删除&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;tr [-ds] STR
&lt;&#x2F;span&gt;&lt;span&gt;d: 删除输入数据的中的STR
&lt;&#x2F;span&gt;&lt;span&gt;s: 替换重复的字符
&lt;&#x2F;span&gt;&lt;span&gt;# 例
&lt;&#x2F;span&gt;&lt;span&gt;last | tr &amp;#39;[a-z]&amp;#39; &amp;#39;A-Z&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;将last输出的数据中的所有小写字符替换为大写字符
&lt;&#x2F;span&gt;&lt;span&gt;SPPU     UUZ1                          TVF MBS 13 18:45   TUJMM MPHHFE JO   
&lt;&#x2F;span&gt;&lt;span&gt;SFCPPU   TZTUFN CPPU  3.10.0-693.17.1. TVF MBS 13 18:45 - 18:47  (00:01)    
&lt;&#x2F;span&gt;&lt;span&gt;SPPU     UUZ1                          TVF MBS 13 10:55 - 13:15  (02:20)    
&lt;&#x2F;span&gt;&lt;span&gt;SFCPPU   TZTUFN CPPU  3.10.0-693.17.1. TVF MBS 13 10:54 - 18:47  (07:52)    
&lt;&#x2F;span&gt;&lt;span&gt;SPPU     UUZ1                          MPO MBS 12 18:33 - 19:35  (01:02)  
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;cat &#x2F;etc&#x2F;passwd | tr -d &amp;#39;:&amp;#39;
&lt;&#x2F;span&gt;&lt;span&gt;将cat &#x2F;etc&#x2F;passwd输出的数据中的&amp;#39;:&amp;#39;全部删除
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;rootx00root&#x2F;root&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;binx11bin&#x2F;bin&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;daemonx22daemon&#x2F;sbin&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;admx34adm&#x2F;var&#x2F;adm&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;lpx47lp&#x2F;var&#x2F;spool&#x2F;lpd&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;col命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;col [-xb]
&lt;&#x2F;span&gt;&lt;span&gt;-x: 将tab键替换为等长的空个
&lt;&#x2F;span&gt;&lt;span&gt;-b: 在文字内由反斜杠时，仅保留反斜杠后接的那个字符
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;cat -A ~&#x2F;.bashrc
&lt;&#x2F;span&gt;&lt;span&gt;# 使用cat -A可以讲输出中所有的特殊按键
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;# Source global definitions$
&lt;&#x2F;span&gt;&lt;span&gt;if [ -f &#x2F;etc&#x2F;bashrc ]; then$
&lt;&#x2F;span&gt;&lt;span&gt;^I. &#x2F;etc&#x2F;bashrc$
&lt;&#x2F;span&gt;&lt;span&gt;fi$
&lt;&#x2F;span&gt;&lt;span&gt;# 注意这里有个^I就是tab字符。
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;cat -A ~&#x2F;.bashrc | col -x
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;# Source global definitions$
&lt;&#x2F;span&gt;&lt;span&gt;if [ -f &#x2F;etc&#x2F;bashrc ]; then$
&lt;&#x2F;span&gt;&lt;span&gt;    . &#x2F;etc&#x2F;bashrc$
&lt;&#x2F;span&gt;&lt;span&gt;# tab字符不再出现
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;join命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;用于对两个文件按照某一个字符或者字段进行按行连接&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;join [-ti12] file1 file2
&lt;&#x2F;span&gt;&lt;span&gt;-t: 选择分割字符，并且对比“第一个字段”的数据，如果两个文件相同，则将两条数据连成一行，并将第一个字段放在最前
&lt;&#x2F;span&gt;&lt;span&gt;-i: 忽略大小写
&lt;&#x2F;span&gt;&lt;span&gt;-1: 表示第一个文件
&lt;&#x2F;span&gt;&lt;span&gt;-2: 表示第二个文件
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;# 例1
&lt;&#x2F;span&gt;&lt;span&gt;head -n 3 &#x2F;etc&#x2F;passwd &#x2F;etc&#x2F;shadow
&lt;&#x2F;span&gt;&lt;span&gt;# 先查看这两个文件前三行数据
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; &#x2F;etc&#x2F;passwd &amp;lt;==
&lt;&#x2F;span&gt;&lt;span&gt;root:x:0:0:root:&#x2F;root:&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;bin:x:1:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;daemon:x:2:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;==&amp;gt; &#x2F;etc&#x2F;shadow &amp;lt;==
&lt;&#x2F;span&gt;&lt;span&gt;root:&amp;lt;密码太长，我忽略了方便查看&amp;gt;:17593:0:99999:7:::
&lt;&#x2F;span&gt;&lt;span&gt;bin:*:17110:0:99999:7:::
&lt;&#x2F;span&gt;&lt;span&gt;daemon:*:17110:0:99999:7:::
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;join -t &amp;#39;:&amp;#39; &#x2F;etc&#x2F;passwd &#x2F;etc&#x2F;shadow | head -n 3
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;root:x:0:0:root:&#x2F;root:&#x2F;bin&#x2F;bash:&amp;lt;密码太长，我忽略了方便查看&amp;gt;:17593:0:99999:7:::
&lt;&#x2F;span&gt;&lt;span&gt;bin:x:1:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin:*:17110:0:99999:7:::
&lt;&#x2F;span&gt;&lt;span&gt;daemon:x:2:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin:*:17110:0:99999:7:::
&lt;&#x2F;span&gt;&lt;span&gt;#  我们可以看到，按照&amp;#39;:&amp;#39;分割，并且默认一第一个字段进行连接
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;# 例2
&lt;&#x2F;span&gt;&lt;span&gt;我们知道&#x2F;etc&#x2F;passwd中第四个字段是GID，而&#x2F;etc&#x2F;group中第三个字段是GID，我们就可以像如下进行整合：
&lt;&#x2F;span&gt;&lt;span&gt;join -t &amp;#39;:&amp;#39; -1 4 &#x2F;etc&#x2F;passwd -2 3 &#x2F;etc&#x2F;group | head -n 3
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;0:root:x:0:root:&#x2F;root:&#x2F;bin&#x2F;bash:root:x:
&lt;&#x2F;span&gt;&lt;span&gt;1:bin:x:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin:bin:x:
&lt;&#x2F;span&gt;&lt;span&gt;2:daemon:x:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin:daemon:x:
&lt;&#x2F;span&gt;&lt;span&gt;# 我们可以看到，将我们需要的字段提到了最前
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;paste命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;直接讲两个文件中的数据按行连接
&lt;&#x2F;span&gt;&lt;span&gt;paste [-d] file1 file2
&lt;&#x2F;span&gt;&lt;span&gt;-d: 设定每行数据连接的字符，默认为tab
&lt;&#x2F;span&gt;&lt;span&gt;paste &#x2F;etc&#x2F;passwd &#x2F;etc&#x2F;group | head -n 3
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;root:x:0:0:root:&#x2F;root:&#x2F;bin&#x2F;bash	root:x:0:
&lt;&#x2F;span&gt;&lt;span&gt;bin:x:1:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin	bin:x:1:
&lt;&#x2F;span&gt;&lt;span&gt;daemon:x:2:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin	daemon:x:2:
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;expand命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;expand [-t] file
&lt;&#x2F;span&gt;&lt;span&gt;-t: 后面接数字，代表了将一个tab替换为多少个空格键
&lt;&#x2F;span&gt;&lt;span&gt;# 例
&lt;&#x2F;span&gt;&lt;span&gt;cat -A ~&#x2F;.bashrc
&lt;&#x2F;span&gt;&lt;span&gt;# 使用cat -A可以讲输出中所有的特殊按键
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;# Source global definitions$
&lt;&#x2F;span&gt;&lt;span&gt;if [ -f &#x2F;etc&#x2F;bashrc ]; then$
&lt;&#x2F;span&gt;&lt;span&gt;^I. &#x2F;etc&#x2F;bashrc$
&lt;&#x2F;span&gt;&lt;span&gt;fi$
&lt;&#x2F;span&gt;&lt;span&gt;注意看有个^I，是tab符号
&lt;&#x2F;span&gt;&lt;span&gt;cat -A ~&#x2F;.bashrc | expand -t 10 -(标准输入) | cat -A
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;# Source global definitions$
&lt;&#x2F;span&gt;&lt;span&gt;if [ -f &#x2F;etc&#x2F;bashrc ]; then$
&lt;&#x2F;span&gt;&lt;span&gt;          . &#x2F;etc&#x2F;bashrc$
&lt;&#x2F;span&gt;&lt;span&gt;fi$
&lt;&#x2F;span&gt;&lt;span&gt;我们可以看到原先的tab变为了10个空格
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux Bash命令杂记(cut sort uniq wc tee)</title>
        <published>2018-03-12T00:00:00+00:00</published>
        <updated>2018-03-12T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Linux Bash命令杂记(cut sort uniq wc tee)/"/>
        <id>https://zhen.wang/article/Linux Bash命令杂记(cut sort uniq wc tee)/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Linux Bash命令杂记(cut sort uniq wc tee)/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;strong&gt;数据流重定向&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;标准输入（stdin）：代码为0，使用&amp;lt;或&amp;lt;&amp;lt;；
&lt;&#x2F;span&gt;&lt;span&gt;标准输出（stdout）：代码为1，使用&amp;gt;或&amp;gt;&amp;gt;；
&lt;&#x2F;span&gt;&lt;span&gt;标准错误输出（stderr）：代码为2，使用2&amp;gt;或2&amp;gt;&amp;gt;；
&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;：覆盖的方式，&amp;gt;&amp;gt;：追加的方式
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如果想要一般输出与错误输出同时输入到某一个文件，如果采取如下的方式进行输出是错误的：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;输出数据  1&amp;gt; list 2&amp;gt; list
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如果按照上面的方式输出到list文件中时而没有采用特殊的语法，会因为两个输出进程的同步问题，导致正确的数据与错误的数据可能会交叉的输入到list文件中。正确的方式应该如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;输出数据 &amp;gt; list 2&amp;gt;&amp;amp;1
&lt;&#x2F;span&gt;&lt;span&gt;# 或者是
&lt;&#x2F;span&gt;&lt;span&gt;输出数据 &amp;amp;&amp;gt; list
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;命令执行&amp;amp;&amp;amp; ||&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;cmd1 &amp;amp;&amp;amp; cmd2
&lt;&#x2F;span&gt;&lt;span&gt;若cmd1执行完毕且正确执行($?==0)，则执行cmd2
&lt;&#x2F;span&gt;&lt;span&gt;若cmd1执行完毕且错误执行($?!=0)，则不执行cmd2
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;cmd1 || cmd2
&lt;&#x2F;span&gt;&lt;span&gt;若cmd1执行完毕且执行正确($?==0)，则不执行cmd2
&lt;&#x2F;span&gt;&lt;span&gt;若cmd1执行完毕且执行错误($?!=0)，则执行cmd2
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;cut命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;cut命令按行数据进行处理，常用的方式如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;#参数 -d -f（组合使用）
&lt;&#x2F;span&gt;&lt;span&gt;输出数据 | cut -d &amp;#39;分个字符&amp;#39; -f fields
&lt;&#x2F;span&gt;&lt;span&gt;# 例
&lt;&#x2F;span&gt;&lt;span&gt;str=ni:hao:ma:?
&lt;&#x2F;span&gt;&lt;span&gt;echo $str | cut -d &amp;#39;:&amp;#39; -f 2
&lt;&#x2F;span&gt;&lt;span&gt;表示将echo出的str字符串按照&amp;#39;:&amp;#39;字符分割，且取第2个字段
&lt;&#x2F;span&gt;&lt;span&gt;得到的结果是
&lt;&#x2F;span&gt;&lt;span&gt;hao
&lt;&#x2F;span&gt;&lt;span&gt;# 补充
&lt;&#x2F;span&gt;&lt;span&gt;-f 1,3 代表取第1和第3字段，输出 ni:man
&lt;&#x2F;span&gt;&lt;span&gt;-f 1-3 取1到3字段，输出 ni:hao:ma
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;# 参数 -c
&lt;&#x2F;span&gt;&lt;span&gt;输出数据 | cut -c 字符范围
&lt;&#x2F;span&gt;&lt;span&gt;# 例
&lt;&#x2F;span&gt;&lt;span&gt;str=hello
&lt;&#x2F;span&gt;&lt;span&gt;echo $str | cut -c 1
&lt;&#x2F;span&gt;&lt;span&gt;输出
&lt;&#x2F;span&gt;&lt;span&gt;h
&lt;&#x2F;span&gt;&lt;span&gt;# 补充
&lt;&#x2F;span&gt;&lt;span&gt;-c 1-，输出 hello
&lt;&#x2F;span&gt;&lt;span&gt;-c 1-3，输出 hel
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;sort命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;head -4 &#x2F;etc&#x2F;passswd
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;root:x:0:0:root:&#x2F;root:&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;bin:x:1:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;daemon:x:2:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;adm:x:3:4:adm:&#x2F;var&#x2F;adm:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;我们可以看到并没有按照首字母排序
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;head -4 &#x2F;etc&#x2F;passwd | sort
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;adm:x:3:4:adm:&#x2F;var&#x2F;adm:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;bin:x:1:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;daemon:x:2:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;root:x:0:0:root:&#x2F;root:&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;我们可以看到已经按照首字母排序了
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;同样，我们可以指定想按照哪一个字段来排序，
&lt;&#x2F;span&gt;&lt;span&gt;head &#x2F;etc&#x2F;passwd | sort -t &amp;#39;:&amp;#39; -k 3
&lt;&#x2F;span&gt;&lt;span&gt;# 不看前4行了，准备输出所有行
&lt;&#x2F;span&gt;&lt;span&gt;# 将输出按照类型&amp;#39;:&amp;#39;分割(-t &amp;#39;:&amp;#39;)，并且取第3个字段(-k 3)
&lt;&#x2F;span&gt;&lt;span&gt;# 然而此时的字段依然是按照字符进行，如本测试机上输出的结果注意看第二行：
&lt;&#x2F;span&gt;&lt;span&gt;root:x:0:0:root:&#x2F;root:&#x2F;bin&#x2F;bash
&lt;&#x2F;span&gt;&lt;span&gt;operator:x:11:0:operator:&#x2F;root:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;bin:x:1:1:bin:&#x2F;bin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;daemon:x:2:2:daemon:&#x2F;sbin:&#x2F;sbin&#x2F;nologin
&lt;&#x2F;span&gt;&lt;span&gt;# 注意第三个字段，11跑到了2前面去了，因为字符串11排在2前面
&lt;&#x2F;span&gt;&lt;span&gt;此时我们需要加上 -n 参数提示按照数字进行
&lt;&#x2F;span&gt;&lt;span&gt;head &#x2F;etc&#x2F;passwd | sort -t &amp;#39;:&amp;#39; -k 3 -n
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;last命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;# 该命令用来列出目前与过去登录系统的用户相关信息
&lt;&#x2F;span&gt;&lt;span&gt;last
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;root     tty1                          Mon Mar 12 18:33   still logged in   
&lt;&#x2F;span&gt;&lt;span&gt;reboot   system boot  3.10.0-693.17.1. Mon Mar 12 18:33 - 19:02  (00:29)    
&lt;&#x2F;span&gt;&lt;span&gt;root     tty1                          Sat Mar 10 20:18 - 20:18  (00:00)    
&lt;&#x2F;span&gt;&lt;span&gt;reboot   system boot  3.10.0-693.17.1. Sat Mar 10 20:18 - 20:18  (00:00)    
&lt;&#x2F;span&gt;&lt;span&gt;root     tty1                          Fri Mar  9 19:10 - 20:50  (01:40)    
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;span&gt;其中：
&lt;&#x2F;span&gt;&lt;span&gt;第一列：用户名
&lt;&#x2F;span&gt;&lt;span&gt;第二列：终端位置。(pts&#x2F;0通过ssh或者telnet远程连接的用户，tty：直接连接到计算机或者本地用户)
&lt;&#x2F;span&gt;&lt;span&gt;第三列：登陆IP或者内核（看到0.0或者什么都没有，意味着用户通过本地终端连接，除了重启，内核版本会显示在状态中）
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;第四列：开始时间(如：sun apr 3 ：四月三号星期天)
&lt;&#x2F;span&gt;&lt;span&gt;第五列：结束时间（still login in 还未退出，down：直到正常关机，crash：直到强制关机）
&lt;&#x2F;span&gt;&lt;span&gt;第六列:持续时间
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;uniq命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;last | cut -d &amp;#39; &amp;#39; -f 1 | sort | uniq
&lt;&#x2F;span&gt;&lt;span&gt;# 先取用户名，然后排序，最后去重
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;reboot
&lt;&#x2F;span&gt;&lt;span&gt;root
&lt;&#x2F;span&gt;&lt;span&gt;wtmp
&lt;&#x2F;span&gt;&lt;span&gt;zhen
&lt;&#x2F;span&gt;&lt;span&gt;# 加上 -c 显示统计
&lt;&#x2F;span&gt;&lt;span&gt;last | cut -d &amp;#39; &amp;#39; -f 1 | sort | uniq -c
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;1
&lt;&#x2F;span&gt;&lt;span&gt;27 reboot
&lt;&#x2F;span&gt;&lt;span&gt;26 root
&lt;&#x2F;span&gt;&lt;span&gt;1 wtmp
&lt;&#x2F;span&gt;&lt;span&gt;3 zhen
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;务必注意，uniq命令是通过叠加去重&lt;strong&gt;相邻&lt;&#x2F;strong&gt;的字符串，如果你不首先进行排序，那么会出现下面的情况：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;      1 root
&lt;&#x2F;span&gt;&lt;span&gt;      1 reboot
&lt;&#x2F;span&gt;&lt;span&gt;      1 root
&lt;&#x2F;span&gt;&lt;span&gt;      1 reboot
&lt;&#x2F;span&gt;&lt;span&gt;      1 root
&lt;&#x2F;span&gt;&lt;span&gt;      1 reboot
&lt;&#x2F;span&gt;&lt;span&gt;      1 root
&lt;&#x2F;span&gt;&lt;span&gt;      1 reboot
&lt;&#x2F;span&gt;&lt;span&gt;      1 zhen
&lt;&#x2F;span&gt;&lt;span&gt;      1 root
&lt;&#x2F;span&gt;&lt;span&gt;      1 reboot
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;wc命令&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;wc [-lwm]
&lt;&#x2F;span&gt;&lt;span&gt;-l: 仅列出行
&lt;&#x2F;span&gt;&lt;span&gt;-w: 仅列出多少个英文单词
&lt;&#x2F;span&gt;&lt;span&gt;-m: 仅列出多少个字符
&lt;&#x2F;span&gt;&lt;span&gt;head &#x2F;etc&#x2F;passwd | wc
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;  10  10  385
&lt;&#x2F;span&gt;&lt;span&gt;# 分别代表行数，词数，字符数（这里10个“词”应该是因为每一行没有空格的原因，wc统计是按空格来分词的）
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;tee双向重定向&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;由前面的数据流我们可以知道，我们在将数据定向时，如果不采取特殊的操作，数据要么输出到屏幕，要么输出到文件或者是设备中，没有办法，既输出到屏幕有输出到文件中；又或者是，我们想要对数据进行处理存放到一个文件中，但是同时对原始数据又存到另一个文件中。使用tee命令，我们就可以做到。&lt;&#x2F;p&gt;
&lt;p&gt;例如，我们使用last命令首先要把数据存放到last.log中，同时要对用户去重并输出到屏幕上：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;last | tee [-a 追加] last.log | cut -d &amp;#39; &amp;#39; -f 1 | sort | uniq
&lt;&#x2F;span&gt;&lt;span&gt;# output
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;reboot
&lt;&#x2F;span&gt;&lt;span&gt;root
&lt;&#x2F;span&gt;&lt;span&gt;wtmp
&lt;&#x2F;span&gt;&lt;span&gt;zhen
&lt;&#x2F;span&gt;&lt;span&gt;# 同时我们打开last.log文件可以看到没有做任何处理的原始数据
&lt;&#x2F;span&gt;&lt;span&gt;root     tty1                          Mon Mar 12 18:33   still logged in   
&lt;&#x2F;span&gt;&lt;span&gt;reboot   system boot  3.10.0-693.17.1. Mon Mar 12 18:33 - 19:29  (00:56)    
&lt;&#x2F;span&gt;&lt;span&gt;root     tty1                          Sat Mar 10 20:18 - 20:18  (00:00)    
&lt;&#x2F;span&gt;&lt;span&gt;reboot   system boot  3.10.0-693.17.1. Sat Mar 10 20:18 - 20:18  (00:00)    
&lt;&#x2F;span&gt;&lt;span&gt;root     tty1                          Fri Mar  9 19:10 - 20:50  (01:40)    
&lt;&#x2F;span&gt;&lt;span&gt;...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Spring自动装配歧义性笔记</title>
        <published>2018-03-11T00:00:00+00:00</published>
        <updated>2018-03-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Spring自动装配歧义性笔记/"/>
        <id>https://zhen.wang/article/Spring自动装配歧义性笔记/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Spring自动装配歧义性笔记/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;&lt;a href=&quot;&#x2F;2018&#x2F;03&#x2F;10&#x2F;Spring-Bean%E8%A3%85%E9%85%8D%E7%AC%94%E8%AE%B0&#x2F;&quot;&gt;前情提要&lt;&#x2F;a&gt;，如果系统中存在两个都实现了同一接口的类，Spring在进行@Autowired自动装配的时候，会选择哪一个？如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 一下两个类均被标记为bean
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Component
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;CD &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Playable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;play&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;CD is playing...&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Component
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Video &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Playable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Override
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fa1b3;&quot;&gt;play&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;() {
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;(&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Video is playing...&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    }
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;配置类仅打开自动扫描
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Configuration
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ComponentScan&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;basePackages &lt;&#x2F;span&gt;&lt;span&gt;= &amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;zhen&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;
&lt;&#x2F;span&gt;&lt;span&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MyConfig &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;测试类
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;RunWith&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;SpringJUnit4ClassRunner&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;ContextConfiguration&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;classes &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MyConfig&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;class&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MyConfigTest &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Autowired
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Playable&lt;&#x2F;span&gt;&lt;span&gt; playable;
&lt;&#x2F;span&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Test
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public&lt;&#x2F;span&gt;&lt;span&gt; void &lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;checkNULL&lt;&#x2F;span&gt;&lt;span&gt;() {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Assert&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;assertNotNull&lt;&#x2F;span&gt;&lt;span&gt;(playable);
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;此时再次运行测试类会发现，FAILD并且报错：&lt;&#x2F;p&gt;
&lt;p&gt;Unsatisfied dependency expressed through field &#x27;playable&#x27;; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type &#x27;zhen.Playable&#x27; available: &lt;strong&gt;expected single matching bean but found 2: CD,video&lt;&#x2F;strong&gt; &#x2F;&#x2F;  找到了两个都bean都能匹配&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;自动装配歧义性问题&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;上面的异常就是出现了歧义性。Spring为我们扫描了我们代码中的bean（这个部分是没有问题的），但是，在自动装配的过程中却由于歧义性而报错，并且，造成这样的歧义性还有由于Autowired这个注解仅仅按照类型进行装配——上面的CD与Video都实现了Playable接口，Autowired注解仅告诉Spring在测试类中的playable接受一个Playable类型的对象但是这里有两个bean：CD、video都是Playable类型的，所以Spring不知道。&lt;&#x2F;p&gt;
&lt;p&gt;为了解决这个问题，我们需要通过一定的手段来限定：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;声明首选的bean
&lt;&#x2F;span&gt;&lt;span&gt;限定自动转配的bean
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;声明首选的bean&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;根据名字我们很容易理解，就是声明在有歧义性情况下，Spring到底选择哪一个bean来装配。方式就是在bean组件下添加@Primary注解,例如在原先的CD的@Component下加上首选注解，再次运行测试代码，PASS。但是，这种方式通常只在同类型bean较少的或者是系统简单的情况使用，而且还存在一个情况：假如目前有两位开发人员，在各自的环境编写bean，他们都希望自己的bean是Primary的，都加该注解，实际上还是会报错，因为系统现在同样有两个Primary bean，Spring还是不能判断选择哪一个bean注入。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;限定自动装配的bean——@Qualifier注解&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;首先，我们可以通过在@Component中加入字符串来更明确的指定bean id而不是使用Spring的默认bean id策略。就像如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Component&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;myCD&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;CD &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Playable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Component&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;myVideo&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Video &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Playable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;当这样指定以后，我们在自动转配的地方，使用@Qualifier(&quot;指定id&quot;)来限定我们要注入的确定的bean：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Autowired
&lt;&#x2F;span&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Qualifier&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;myCD&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Playable&lt;&#x2F;span&gt;&lt;span&gt; playable;
&lt;&#x2F;span&gt;&lt;span&gt;  ...
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;再次运行不会报错。&lt;&#x2F;p&gt;
&lt;p&gt;关于@Qualifier，最佳的情形应该是来标记bean特性。但是，如果多个bean都有相同的特性，都是用了相同的标记的@Qualifier注解，那么同样又会出现歧义性问题。所以我们又要添加新的@Qualifier注解来进一步限定，这样做没有问题，但是Java语法规定，不允许在同一条目上重复出现相同类型的多个注解。你&lt;strong&gt;不能&lt;&#x2F;strong&gt;这么做：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 编译器会报错
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Qualifier&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;myCD&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Qaulifier&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;JayChou&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;CD &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Playable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;  ...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;为了结局这样的问题，我们可以创建自己的注解：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Target&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;FIELD&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;CONSTRUCTOR&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;TYPE&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;METHOD&lt;&#x2F;span&gt;&lt;span&gt;}) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;字段注解  
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Retention&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;RetentionPolicy&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RUNTIME&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;在运行期保留注解信息
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Qualifier &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F; 需要使用@Qualifier注解来限定
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public @interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;MyCD &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Target&lt;&#x2F;span&gt;&lt;span&gt;({&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;FIELD&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;CONSTRUCTOR&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;TYPE&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;ElementType&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;METHOD&lt;&#x2F;span&gt;&lt;span&gt;})
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Retention&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;RetentionPolicy&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;RUNTIME&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Qualifier
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public @interface &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;JayChou &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;如此定义了注解以后，我们就可以在原先的@Component下如下定义：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Component
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;MyCD
&lt;&#x2F;span&gt;&lt;span&gt;@&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;JayChou
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;public class &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;CD &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;implements &lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;Playable &lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;{
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;  ...
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#eff1f5;&quot;&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;并且在测试类下如下声明：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;Autowired
&lt;&#x2F;span&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;MyCD
&lt;&#x2F;span&gt;&lt;span&gt;    @&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;JayChou
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Playable&lt;&#x2F;span&gt;&lt;span&gt; playable;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;测试通过！&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Spring Bean装配笔记</title>
        <published>2018-03-10T00:00:00+00:00</published>
        <updated>2018-03-10T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Spring Bean装配笔记/"/>
        <id>https://zhen.wang/article/Spring Bean装配笔记/</id>
        
        <summary type="html">&lt;p&gt;Spring中的Bean是一个很重要的概念。Spring作为一个Bean容器，它可以管理对象和对象之间的依赖关系，我们不需要自己建立对象，把这部分工作全部转交给容器完成，具有低耦合，对代码没有侵略性。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Linux下关于用户账户的几个文件解析</title>
        <published>2018-03-09T00:00:00+00:00</published>
        <updated>2018-03-09T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Linux下关于用户账户的几个文件解析/"/>
        <id>https://zhen.wang/article/Linux下关于用户账户的几个文件解析/</id>
        
        <summary type="html">&lt;p&gt;Linux是一个多用户系统，但是对于一个多用户共存的系统中，当然不能够出现用户相互越权等一系列的安全问题，所以如何正确的管理账户成为了Linux系统中至关重要的一环。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>CentOS7下Hadoop伪分布式环境搭建</title>
        <published>2018-03-08T00:00:00+00:00</published>
        <updated>2018-03-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/CentOS7下Hadoop伪分布式环境搭建/"/>
        <id>https://zhen.wang/article/CentOS7下Hadoop伪分布式环境搭建/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/CentOS7下Hadoop伪分布式环境搭建/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;前期准备&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1.配置hostname(可选，了解)&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在CentOS中，有三种定义的主机名:静态的（static），瞬态的（transient），和灵活的（pretty）。“静态”主机名也称为内核主机名，是系统在启动时从&#x2F;etc&#x2F;hostname自动初始化的主机名。“瞬态”主机名是在系统运行时临时分配的主机名，例如，通过DHCP或mDNS服务器分配。静态主机名和瞬态主机名都遵从作为互联网域名同样的字符限制规则。而另一方面，“灵活”主机名则允许使用自由形式（包括特殊&#x2F;空白字符）的主机名，以展示给终端用户（如Linuxidc）。&lt;&#x2F;p&gt;
&lt;p&gt;在CentOS7以前，配置主机的静态hostname是在&#x2F;etc&#x2F;sysconfig&#x2F;network中配置HOSTNAME字段值来配置，而CentOS7之后若要配置静态的hostname是需要在&#x2F;etc&#x2F;hostname中进行。&lt;&#x2F;p&gt;
&lt;p&gt;进入Linux系统，命令行下输入hostname可以看到当前的hostname，而通常默认的hostname是local.localadmin。&lt;&#x2F;p&gt;
&lt;p&gt;本次试验环境在CentOS7下，所以我们编辑&#x2F;etc&#x2F;hostname文件，试验hostname为：hadoop.w4ng，填入其中，重启Linux，可以看到已经生效。
&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2018-03-08-hadoop-install&#x2F;hostname.png&quot; alt=&quot;hostname.png&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;2.配置静态IP&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;同样，在CentOS7以后，其网卡配置已经有原先的&#x2F;etc&#x2F;sysconfig&#x2F;network&#x2F;network-scripts下面的ifcfg-eth0等改名为乐ifcfg-enpXsY（en表示ethnet，p表示pci设备，s表示soket）
&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2018-03-08-hadoop-install&#x2F;ll-network-scripts.png&quot; alt=&quot;ll-network-scripts.png&quot; &#x2F;&gt;
本人这里有两个ifcfg文件是因为配置了两块网卡&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;blog.csdn.net&#x2F;wangshfa&#x2F;article&#x2F;details&#x2F;8813505&quot;&gt;分别做NAT以及与虚拟机Host-Only两个功能，实现双网卡上网&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;打开ifcfg-enp0s8，配置如下：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;DEVICE=enp0s8 #设备名
&lt;&#x2F;span&gt;&lt;span&gt;HWADDR=08:00:27:10:6B:6B #硬件地址
&lt;&#x2F;span&gt;&lt;span&gt;TYPE=Ethernet #类型
&lt;&#x2F;span&gt;&lt;span&gt;BOOTPROTO=static #静态IP(必备)
&lt;&#x2F;span&gt;&lt;span&gt;IPADDR=192.168.56.88 #IP地址
&lt;&#x2F;span&gt;&lt;span&gt;NETMASK=255.255.255.0 #子网掩码
&lt;&#x2F;span&gt;&lt;span&gt;ONBOOT=yes #设备开机自动启动该网卡
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;3.配置hosts&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;打开&#x2F;etc&#x2F;hosts
配置为如下的：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
&lt;&#x2F;span&gt;&lt;span&gt;::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
&lt;&#x2F;span&gt;&lt;span&gt;192.168.56.88   hadoop.w4ng
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;配置hosts的理由是后期hadoop配置中相关的主机填写我们都是使用域名的形式，而IP地址与域名的转换在这里进行查询（还有DNS，但是这里不讨论）。&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;4.关闭防火墙&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;CentOS7与6的防火墙不一样。在7中使用firewall来管理防火墙，而6是使用iptables来进行管理的。&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.cnblogs.com&#x2F;silent2012&#x2F;archive&#x2F;2015&#x2F;07&#x2F;28&#x2F;4682770.html&quot;&gt;当然，我们可以卸载7的firewall安装6的iptables来管理&lt;&#x2F;a&gt;。本人就切换回了6的防火墙管理方式。&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;[root@localhost ~]#servcie iptables stop  # 临时关闭防火墙
&lt;&#x2F;span&gt;&lt;span&gt;[root@localhost ~]#chkconfig iptables off # 永久关闭防火墙
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;strong&gt;5.JDK与Hadoop的安装&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;www.oracle.com&#x2F;technetwork&#x2F;java&#x2F;javase&#x2F;downloads&#x2F;index.html&quot;&gt;下载JDK8&lt;&#x2F;a&gt;
&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;hadoop.apache.org&#x2F;releases.html&quot;&gt;下载Hadoop3-binary&lt;&#x2F;a&gt;
下载完毕将文件传到主机中。&lt;&#x2F;p&gt;
&lt;p&gt;在&#x2F;usr&#x2F;local&#x2F;下创建java文件夹，并将JDK解压至该文件夹下。
在根目录下创建&#x2F;bigdata文件夹，并将Hadoop解压至其中。&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;解压命令 tar -zxv -f [原压缩文件.tar.gz] -C [目标文件夹目录] # 实际命令没有中括号，其次，命令参数重-z对应gz压缩文件，若为bz2则使用-j
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在JDK解压完成后，在~&#x2F;.bash_profile中配置环境变量 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;http:&#x2F;&#x2F;blog.csdn.net&#x2F;field_yang&#x2F;article&#x2F;details&#x2F;51087178&quot;&gt;点这里看&#x2F;etc&#x2F;bashrc、&lt;del&gt;&#x2F;.bashrc、&lt;&#x2F;del&gt;&#x2F;.bash_profile关系&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;shell&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-shell &quot;&gt;&lt;code class=&quot;language-shell&quot; data-lang=&quot;shell&quot;&gt;&lt;span&gt;export JAVA_HOME=&#x2F;usr&#x2F;local&#x2F;java&#x2F;jdkx.x.x_xxx
&lt;&#x2F;span&gt;&lt;span&gt;export PATH=$PATH:$JAVA_HOME&#x2F;bin
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;配置完成，保存退出并 source ~&#x2F;.bash_profile&lt;&#x2F;p&gt;
&lt;p&gt;hadoop无需配置环境变量&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;6.配置hadoop&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;在hadoop的home下，进入etc文件夹，有五个主要的文件需要进行配置：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;hadoop-env.sh
&lt;&#x2F;span&gt;&lt;span&gt;core-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;hdfs-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;mapred-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;yarn-site.xml
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;基本配置如下&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;1.配置 hadoop-env.sh
&lt;&#x2F;span&gt;&lt;span&gt;export JAVA_HOME
&lt;&#x2F;span&gt;&lt;span&gt;#找到该处，填写上上面配置的JAVA_HOME，因为hadoop是基于Java的，需要Java的环境
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;2.配置 core-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;name&amp;gt;fs.defaultFS&amp;lt;&#x2F;name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;value&amp;gt;hdfs:&#x2F;&#x2F;hostnameXXX:9000&amp;lt;&#x2F;value&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;!-- 配置hadoop文件系统目录 --&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;name&amp;gt;hadoop.tmp.dir&amp;lt;&#x2F;name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;value&amp;gt;&#x2F;bigData&#x2F;tmp&amp;lt;&#x2F;value&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;3.配置 hdfs-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;name&amp;gt;dfs.replication&amp;lt;&#x2F;name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;value&amp;gt;1&amp;lt;&#x2F;value&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;4.配置 mapred-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;name&amp;gt;mapreduce.framework.name&amp;lt;&#x2F;name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;value&amp;gt;yarn&amp;lt;&#x2F;value&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;5.配置 yarn-site.xml
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;name&amp;gt;yarn.resourecemanager.hostname&amp;lt;&#x2F;name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;value&amp;gt;hostnameXXX&amp;lt;&#x2F;value&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;name&amp;gt;yarn.nodemanager.aux-services&amp;lt;&#x2F;name&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;lt;value&amp;gt;mapreduce_shuffle&amp;lt;&#x2F;value&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &amp;lt;&#x2F;property&amp;gt;
&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&#x2F;configuration&amp;gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;然后配置相关服务启动过程中需要的配置变量：
进入${HADOOP_HOME}&#x2F;sbin中，在start-dfs.sh与stop-dfs.sh中添加字段：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;HDFS_DATANODE_USER=root
&lt;&#x2F;span&gt;&lt;span&gt;HDFS_DATANODE_SECURE_USER=hdfs
&lt;&#x2F;span&gt;&lt;span&gt;HDFS_NAMENODE_USER=root
&lt;&#x2F;span&gt;&lt;span&gt;HDFS_SECONDARYNAMENODE_USER=root
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;在start-yarn.sh与stop-yarn.sh中添加：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;YARN_RESOURCEMANAGER_USER=root
&lt;&#x2F;span&gt;&lt;span&gt;HADOOP_SECURE_DN_USER=yarn
&lt;&#x2F;span&gt;&lt;span&gt;YARN_NODEMANAGER_USER=root
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;配置完成以后，进行hadoop的文件系统格式化，执行&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;${HADOOP_HOME}&#x2F;bin&#x2F;hdfs namenode -format
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;最后是启动服务：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;执行${HADOOP_HOME}&#x2F;sbin&#x2F;start-all.sh  # 他会去调用start-dfs.sh与start-yarn.sh
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;根据配置中我们都是配置的root用户，显然需要我们以root身份进行，且过程中需要root密码。当然，通过ssh免密可以方便很多。启动完成以后，命令行中使用jps命令打印Java进程，会看到下图五个进程（忽略Jps进程）：
&lt;img src=&quot;https:&#x2F;&#x2F;static-res.zhen.wang&#x2F;images&#x2F;post&#x2F;2018-03-08-hadoop-install&#x2F;jps.png&quot; alt=&quot;jps.png&quot; &#x2F;&gt;
当然，Hadoop在服务启动以后以提供web端：&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#2b303b;color:#c0c5ce;&quot;&gt;&lt;code&gt;&lt;span&gt;visit hdfs manage page
&lt;&#x2F;span&gt;&lt;span&gt;xxx.xxx.xxx.xxx:50070
&lt;&#x2F;span&gt;&lt;span&gt;visit yarn manage page
&lt;&#x2F;span&gt;&lt;span&gt;xxx.xxx.xxx.xxx:8088
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>IntelliJ中Spring识别BUG</title>
        <published>2018-03-07T00:00:00+00:00</published>
        <updated>2018-03-07T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/IntelliJ中Spring提示BUG/"/>
        <id>https://zhen.wang/article/IntelliJ中Spring提示BUG/</id>
        
        <summary type="html">&lt;p&gt;最近开始学习Spring，在看《Spring实战4th》3.3“处理自动装配的歧义性”那一部分时，书上提到（也从网上看到了类似的用法）:
通过在一个类上加注@Component以及@Qualifier(&quot;x&quot;)可以为其配置限定符来标识区分同一个接口下的不同实现类，用以在需要进行@Autowired自动装配的地方使用@Qualifier(&quot;x&quot;)来指定特定的实现类对象bean。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>在macOS上通过虚拟机搭建基础CentOS7系统环境</title>
        <published>2018-03-06T00:00:00+00:00</published>
        <updated>2018-03-06T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/在macOS上通过虚拟机搭建基础CentOS系统环境/"/>
        <id>https://zhen.wang/article/在macOS上通过虚拟机搭建基础CentOS系统环境/</id>
        
        <summary type="html">&lt;p&gt;尽管从Mac的Terminal可以看出，macOS与UNIX、Linux或多或少都有血缘关系（shell、bash等），但是在mac进行Linux开发，或者把macOS直接当作Linux来使用依然是说不过去的，这其中包括一些命令行的使用，一些基本的文件夹体系等（如，在Linux上的&#x2F;home目录与在macOS下的&#x2F;Users）不一致。如果想要在macOS上进行Linux的学习，或者进行Linux开发，最完美的方案自然是安装虚拟机。&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Java中的Integer</title>
        <published>2017-08-22T00:00:00+00:00</published>
        <updated>2017-08-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/JavaInteger小记/"/>
        <id>https://zhen.wang/article/JavaInteger小记/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/JavaInteger小记/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;p&gt;众所周知，在Java中，存在着值比较与应用比较两种情况。例如，如下的比较，可以根据值比较与引用比较来跟容易的判断出结果来：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt; a = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;int&lt;&#x2F;span&gt;&lt;span&gt; b = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; s1 = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt; s2 = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;String&lt;&#x2F;span&gt;&lt;span&gt;(&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#a3be8c;&quot;&gt;123&lt;&#x2F;span&gt;&lt;span&gt;&amp;quot;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(a == b); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;true
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(s1 == s2); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;false
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;这里，a与b由于是基本类型，所以Java在比较的时候直接就是按值来比较，而下面的s1与s2则是由于分别指向内容为“123”的字符串对象引用（关于string的细节，见本人的另一篇文章），而这两个字符串的地址并不一样，所以结果是false。&lt;&#x2F;p&gt;
&lt;p&gt;那么，今天要讨论的是，对于Java自动拆装箱的问题的深入探讨。如下所示，请问结果是什么呢？&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; a = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;666&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; b = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;666&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(a == b);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;结果是false，您可能会说，这有什么好问的，Integer对象的比较，引用的比较，而这两个只是值相同，而对象不同的Integer对象罢了，所以当然为false。好，那么我再问你，下面的结果是什么？&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; a = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; b = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;100&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(a == b);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;您可能说，哇，当我傻吗，当然还是false了。可是，结果是true。&lt;&#x2F;p&gt;
&lt;p&gt;为什么同样的情况下，当值变小了，结果就变为true了呢。&lt;&#x2F;p&gt;
&lt;p&gt;其实，Java中，对于可装箱的对象类型，都存在一个1字节的范围：-128到127。在这个范围类的数字，Java认为是常用的数字，所以自动进行了值比较，而不是进行引用的比较。所以，无论是Long还是Integer，只有你的值在-128到127，这两个对象的比较直接按照其所存储的值来进行。就像如下的情况：&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;java&quot; style=&quot;background-color:#2b303b;color:#c0c5ce;&quot; class=&quot;language-java &quot;&gt;&lt;code class=&quot;language-java&quot; data-lang=&quot;java&quot;&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; a = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; b = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; c = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Integer&lt;&#x2F;span&gt;&lt;span&gt; d = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;127&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long&lt;&#x2F;span&gt;&lt;span&gt; e = -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;129&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;L&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long&lt;&#x2F;span&gt;&lt;span&gt; f = -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;129&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;L&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long&lt;&#x2F;span&gt;&lt;span&gt; g = -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;L&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;Long&lt;&#x2F;span&gt;&lt;span&gt; h = -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#d08770;&quot;&gt;128&lt;&#x2F;span&gt;&lt;span style=&quot;color:#b48ead;&quot;&gt;L&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(a == b); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;false
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(c == d); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;true
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(e == f); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;false
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ebcb8b;&quot;&gt;System&lt;&#x2F;span&gt;&lt;span&gt;.out.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#bf616a;&quot;&gt;println&lt;&#x2F;span&gt;&lt;span&gt;(g == h); &lt;&#x2F;span&gt;&lt;span style=&quot;color:#65737e;&quot;&gt;&#x2F;&#x2F;true
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Java String的探讨</title>
        <published>2017-04-18T00:00:00+00:00</published>
        <updated>2017-04-18T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/JavaString探讨/"/>
        <id>https://zhen.wang/article/JavaString探讨/</id>
        
        <summary type="html">&lt;p&gt;关于String相关内容的学习，历来都是Java学习必不可少的一个经历。&lt;&#x2F;p&gt;
&lt;p&gt;以前一直想要好好总结一下String的相关的知识点，苦于没有时间，终于在今天有一个闲暇的时间来好好总结一下，也希望这文章能够加深我对于String相关内容的理解~（ps:在我看来，学习某些知识点的时候把学到的想到的都记录下一方面能够加深自己学习印象，二者能够锻炼锻炼我的文笔~）&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>修改MySql Root密码（包含忘记密码的方式）</title>
        <published>2017-04-04T00:00:00+00:00</published>
        <updated>2017-04-04T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/修改MysqlRoot密码/"/>
        <id>https://zhen.wang/article/修改MysqlRoot密码/</id>
        
        <summary type="html">&lt;p&gt;曾几何时，我也是记得MySQL root密码的人，想要修改root密码还不是轻而易举的事？下面前三种修改改方式都是在记得密码的情况下进行修改，如果你忘记了原本的root，请直接跳至 &lt;strong&gt;终极&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;</summary>
        
    </entry>
    <entry xml:lang="en">
        <title>Java学习路线【转】</title>
        <published>2017-02-21T00:00:00+00:00</published>
        <updated>2017-02-21T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://zhen.wang/article/Java学习路线【转】/"/>
        <id>https://zhen.wang/article/Java学习路线【转】/</id>
        
        <content type="html" xml:base="https://zhen.wang/article/Java学习路线【转】/">&lt;span id=&quot;continue-reading&quot;&gt;&lt;&#x2F;span&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;第一阶段：JavaSE（Java基础部分）&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;Java开发前奏
计算机基本原理，Java语言发展简史以及开发环境的搭建，体验Java程序的开发，环境变量的设置，程序的执行过程，相关反编译工具介绍，java开发工具Eclipse的安装和使用，javadoc的说明。&lt;&#x2F;li&gt;
&lt;li&gt;Java基础语法
Java语法格式，常量和变量，变量的作用域，方法和方法的重载，运算符，程序流程控制，数组和操作数组的类，对数组循环遍历以及针对数组的常用查找、排序算法原理，最后使用Java程序进行功能实现。&lt;&#x2F;li&gt;
&lt;li&gt;面向对象编程
理解对象的本质，以及面向对象，类与对象之间的关系，如何用面向对象的思想分析和解决显示生活中的问题，并java程序的手段编写出来。如何设计类，设计类的基本原则，类的实例化过程，类元素：构造函数、this关键字、方法和方法的参数传递过程、static关键字、内部类，Java的垃圾对象回收机制。对象的三大特性：封装、继承和多态。子类对象的实例化过程、方法的重写和重载、final关键字、抽象类、接口、继承的优点和缺点。 对象的多态性：子类和父类之间的转换、父类纸箱子类的引用、抽象类和接口在多态中的应用、多态优点。常用设计模式如单利、模版等模式。什么是异常 异常的捕捉和抛出 异常捕捉的原则 finally的使用，package的应用import关键字。&lt;&#x2F;li&gt;
&lt;li&gt;多线程应用
多线程的概念，如何在程序中创建多线程(Thread、Runnable)，线程安全问题，线程的同步，线程之间的通讯、死锁问题的剖析。&lt;&#x2F;li&gt;
&lt;li&gt;JavaAPI详解
JavaAPI介绍、String和StringBuffer、各种基本数据类型包装类，System和Runtime类，Date和DateFomat类等。常用的集合类使用如下：Java Collections Framework：Collection、Set、List、ArrayList、Vector、LinkedList、Hashset、TreeSet、Map、HashMap、TreeMap、Iterator、Enumeration等常用集合类API。&lt;&#x2F;li&gt;
&lt;li&gt;IO技术
什么是IO，File及相关类，字节流InputStream和OutputStream，字符流Reader和Writer，以及相应缓冲流和管道流，字节和字符的转化流，包装流，以及常用包装类使用，分析java的IO性能。&lt;&#x2F;li&gt;
&lt;li&gt;网络编程
Java网络编程，网络通信底层协议TCP&#x2F;UDP&#x2F;IP，Socket编程。网络通信常用应用层协议简介：HTTP、FTP等，以及WEB服务器的工作原理。&lt;&#x2F;li&gt;
&lt;li&gt;Java高级特性
递归程序，Java的高级特性：反射、代理和泛型、枚举、Java正则表达式API详解及其应用。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;&lt;strong&gt;第二阶段：数据库技术&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;ORACLE基础管理
Oracle背景简介，数据库的安装，数据库的用户名和密码，客户端登录数据库服务SQLPLUS，数据库基本概。&lt;&#x2F;li&gt;
&lt;li&gt;SQL语句
数据库的创建，表的创建，修改，删除，查询，索引的创建，主从表的建立，数据控制授权和回收，事务控制，查询语句以及运算符的详解，sql中的函数使用。&lt;&#x2F;li&gt;
&lt;li&gt;多表连接和字查询
等值和非等值连接，外连接，自连接；交叉连接，自然连接，using子句连接，完全外连接和左右外连接，子查询使用以及注意事项。&lt;&#x2F;li&gt;
&lt;li&gt;触发器、存储过程
触发器和存储过程使用场合， 通过实例进行详解。&lt;&#x2F;li&gt;
&lt;li&gt;数据库设计优化
WHERE子句中的连接顺序，选择最有效率的表名顺序，SELECT子句中避免使用 ‘ * ’计算记录条数等等。&lt;&#x2F;li&gt;
&lt;li&gt;数据备份与移植
移植技巧，备份方案；导入导出等。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;&lt;strong&gt;第三阶段：JDBC技术&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;JDBC基础
JDBC Connection、Statement、PreparedStatement、CallableStatement、ResultSet等不同类的使用。&lt;&#x2F;li&gt;
&lt;li&gt;连接池技术
了解连接池的概念，掌握连接池的建立、治理、关闭和配置。&lt;&#x2F;li&gt;
&lt;li&gt;ORM与DAO封装
对象关系映射思想，JDBC的DAO封装，实现自己的JDBC。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;&lt;strong&gt;第四阶段：WEB基础技术&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;XML技术
使用jdom和dom4j来对xml文档的解析和生成操作，xml的作用和使用场合。&lt;&#x2F;li&gt;
&lt;li&gt;HTML&#x2F;CSS
Java掌握基本的html标签的格式和使用，css层叠样式表对div的定义，实现对网站布局的基本实现。&lt;&#x2F;li&gt;
&lt;li&gt;JavaScript
了解javascript的基本语法以及相关函数的使用，并结合html页面实现流程控制和页面效果展示。什么是异常 异常的捕捉和抛出 异常捕捉的原则 finally的使用，package的应用import关键字。&lt;&#x2F;li&gt;
&lt;li&gt;JSP&#x2F;Servlet
Servlet和JSP技术、上传下载、Tomcat服务器技术、servlet过滤器和监听器。&lt;&#x2F;li&gt;
&lt;li&gt;JSTL与EL
JSTL核心标签库、函数标签库、格式化标签库、自定义标签技术、EL表达式在jsp页面的使用。&lt;&#x2F;li&gt;
&lt;li&gt;AJAX及框架技术
AJAX框架Jquery渲染页面效果和相关的强大的第三方类库，dwr如何和后台服务进行数据传输，以及页面逻辑控制等。&lt;&#x2F;li&gt;
&lt;li&gt;JSON高级应用
Java使用JSON支持的方式对字符串进行封装和解析，实现页面和Java后台服务的数据通信。&lt;&#x2F;li&gt;
&lt;li&gt;Fckeditor编辑器
FCKEditor在线编辑器技术、配置、处理图片和文件上传。&lt;&#x2F;li&gt;
&lt;li&gt;JavaMail技术
了解域名解析与MX记录、电子邮件工作原理、邮件传输协议：SMTP、POP3、IMAP、邮件组织结构：RFC822邮件格式、MIME协议、邮件编码、复合邮件结构分析、JavaMail API及其体系结构、编程创建邮件内容：简单邮件内容、包含内嵌图片的复杂邮件、包含内嵌图片和附件的复杂邮件。&lt;&#x2F;li&gt;
&lt;li&gt;JfreeChart报表
统计报表；图表处理。&lt;&#x2F;li&gt;
&lt;li&gt;BBS项目实战
采用Jquery+dwr+jsp+servlet+Fckeditor+JfreeChart+tomcat+jdbc(oracle)完成BBS项目的实战。学完此实战你至少已经是拥有近1年开发经验的程序员了，但是你不应该满足现状，继续下去！&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;&lt;strong&gt;第五阶段：WEB主流框架技术（项目实战）&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;Struts2.x
struts2框架的工作原理和架构分析，struts-default.xml与default.properties文件的作用，struts。Xml中引入多个配置文件。OGNL表达式、Struts2 UI和非UI标签、输入校验、使用通配符定义action、动态方法调用、多文件上传、自定义类型转换器、为Action的属性注入值、自定义拦截器、异常处理、使用struts2实现的CRUD操作的案例。&lt;&#x2F;li&gt;
&lt;li&gt;Hibernate
Hibernate应用开发基础； ORM基础理论； 关系映射技术； 性能调优技术； 性能优化 一级缓存 二级缓存 查询缓存 事务与并发 悲观锁、乐观锁。&lt;&#x2F;li&gt;
&lt;li&gt;Spring4.x
Spring IoC技术；Spring AOP技术；Spring声明事务管理；Spring常用功能说明，spring4.x的新特性，Spring整合Struts2和Hibernate3的运用。&lt;&#x2F;li&gt;
&lt;li&gt;Log4j与Junit
Logging API；JUnit单元测试技术； 压力测试技术：badboy进行测试计划跟踪获取以及JMeter压力测试。&lt;&#x2F;li&gt;
&lt;li&gt;在线支付技术
完成支付宝的支付接口的在线支付功能。&lt;&#x2F;li&gt;
&lt;li&gt;电子商务网站实战
采用Spring4.x + Hibernate3 + Struts2 + Jquery + dwr + FckEditor + Tomcat完成电子商务网站实战开发。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol start=&quot;6&quot;&gt;
&lt;li&gt;&lt;strong&gt;第六阶段：WEB高级进阶（项目实战）&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;OpenJPA技术
JPA介绍及开发环境搭建、单表实体映射、一对多&#x2F;多对一、一对一、多对多关联、实体继承、复合主键、JPQL语句、EntityManager API、事务管理，了解一下jpa2.0的新特性以及应用。&lt;&#x2F;li&gt;
&lt;li&gt;Lucene搜索引擎
了解全文搜索原理、全文搜索引擎、什么是OSEM、OSEM框架Compass、基于使用Lucene使用Compass实现全文增量型索引创建和搜索、探索Lucene 3.0以及API。&lt;&#x2F;li&gt;
&lt;li&gt;电子商务重构
此项目采用了Lucene+compass+openJpa+上一版电子商务网站的技术进行重构。&lt;&#x2F;li&gt;
&lt;li&gt;Excel&#x2F;PDF文档处理技术
Java对Excel和pdf文档分别利用poi和itext来进行解析和生成。此技术在企业级系统的报表中经常使用。&lt;&#x2F;li&gt;
&lt;li&gt;OA工作流技术JBPM
工作流是什么、JBPM介绍、JBPM的主要用法、各类节点的用法、任务各种分派方式、JBPM的整体架构原理、工作流定义模型分析、运行期工作流实例模型分析、数据库表模型分析、流程定义管理、流程实例监控、对JBPM的相关接口进行封装，构建自己的工作流应用平台等。&lt;&#x2F;li&gt;
&lt;li&gt;WebService技术
WebService技术原理、WebService技术的应用、Soap服务的创建与管理、WSDL描述文档规范、UDDI注册中心运行原理;使用Axis和Xfire创建WEB服务、Webservice客户端的编写、使用TCPMonitor监听SOAP协议、异构平台的整合。&lt;&#x2F;li&gt;
&lt;li&gt;Linux操作系统
Linux系统安装，卸载、linux使用的核心思想、Linux下的用户管理，文件管理,系统管理、程序的安装，使用，卸载。Linux下作为server的基本应用：web服务器，j2ee服务器，ftp服务器的安装和项目的部署。&lt;&#x2F;li&gt;
&lt;li&gt;CRM项目实战
此项目能了解和熟悉客户关系管理的基本流程以及功能的实现，采用上面几个阶段学到的主流框架实现，同时加入了JBPM的技术。！&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;ol start=&quot;7&quot;&gt;
&lt;li&gt;&lt;strong&gt;第七阶段：大型高并发网站优化解决（项目实战）&lt;&#x2F;strong&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;ul&gt;
&lt;li&gt;如何构建一个高性能网站详解
什么样的网站需要高性能，高性能的指标体系，构建高性能网站需要做哪些工作，注意哪些细节。&lt;&#x2F;li&gt;
&lt;li&gt;SSI技术
什么是SSI，使用他有什么好处，什么样的系统才使用SSI，SSI技术详解和使用，应用到项目中。&lt;&#x2F;li&gt;
&lt;li&gt;静态页面生成技术
什么是静态页，为什么需要静态页以及带来的好处，生成静态页的模版技术Velocity和Freemark，生成静态页的访问规则等。&lt;&#x2F;li&gt;
&lt;li&gt;缓存技术
为什么使用缓存技术，oscache缓存技术的介绍和使用，memcached缓存技术的介绍和使用、两者缓存技术的比较和如何去使用。&lt;&#x2F;li&gt;
&lt;li&gt;经典WEb服务器
什么是web服务器，什么是JavaWeb服务器，他们存在什么关系，当前技术主流中常用的Web服务器有哪些，WEB服务器Apache和Nginx的应用。&lt;&#x2F;li&gt;
&lt;li&gt;Nginx架构实战
什么是反向代理，负载均衡以及集群，在Nginx中如何实现这些高性能的系统架构。&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
        
    </entry>
</feed>
