<?xml version="1.0" encoding="utf-8" standalone="no"?><rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" version="2.0"><channel><title>潘深练的个人网站</title><managingEditor>noemail@noemail.org (biglate)</managingEditor><pubDate>Mon, 6 Oct 2025 15:31:39 GMT</pubDate><generator>Hexo https://hexo.io/</generator><language>en-us</language><itunes:explicit>no</itunes:explicit><itunes:owner><itunes:email>noemail@noemail.org</itunes:email></itunes:owner><item><title>二〇二五中秋：行至中场，人生半熟</title><category>生活</category><category>深圳</category><category>灏灏</category><category>人生伴侣</category><category>一家三口</category><pubDate>Mon, 6 Oct 2025 14:00:00 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2025/10/06/live-001-mid-autumn-festival-in-2025/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/live/2025/october/family-001.png"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>恰人生中章，恰家庭中章，恰职场中章，恰年度中章。来深已两年零四个月，生活依旧是高速运转，只是节奏已悄然变化。我不再像初来乍到时那般患得患失，却也不曾真正松弛，职场OKR、晋升、家庭现金流——像一根无形的鞭子，抽打着每一根神经。易躁成了常态，深夜办公室，也渐渐成了第二居所。爱人则像一位静默的修行者，在职场与家庭之间切换，在工作进击与孩子成长间平衡，用读书、冥想、瑜伽、咖啡、寺庙祈福，最重要是孩子的爱，筑起了一座缓冲焦虑的堤坝。而那个曾经需要我们牵着手过马路的小人儿，如今已系上红领巾，在我们认为正确的轨道上，飞速旋转，时而与我们碰撞出火花，时而让我们倍感欣喜，他开始像一颗快速生长的卫星，在离心与向心间撕扯。在疾驰与旋转中，餐桌交汇瞬间，甚至变得珍贵，往后，恐怕慢不下来。</p><h2 id="对自己"><a href="#对自己" class="headerlink" title="对自己"></a>对自己</h2><p>我喜欢编程，无论是在职场上满足业务需求、技术攻关，或是业余的个人作品创作，我都极具热情。</p><h4 id="1、职场变化"><a href="#1、职场变化" class="headerlink" title="1、职场变化"></a>1、职场变化</h4><p>我始终以实践验证自己对职场的理解和思考，在竞争中能保持中游的表现，所以我延续一贯的作风，有幸在 <a href="https://www.oceanpayment.com/">Oceanpayment</a> 负责系统架构，责任重大，但我饱有热情，以目标驱动，对于岗位涉及的架构规划和团队管理，我尽最大努力去深入解读、付诸行动，并持续改进，把事做极致，达到预期结果，让与我同行的人，能发光发亮，有所成长，有所获得。当然，基于企业期望与自我实现要求，很多时候，致使我在人和事的规划、协调以及处理上，会极度谨慎、苛刻。</p><p>但不管如何，这两年的付出与收获，值得肯定，感谢自己如此努力。</p><p><img src="https://www.panshenlian.com/images/post/live/2025/october/career-001.jpg"></p><h4 id="2、收入、负债和那些让人纠结的资产决策"><a href="#2、收入、负债和那些让人纠结的资产决策" class="headerlink" title="2、收入、负债和那些让人纠结的资产决策"></a>2、收入、负债和那些让人纠结的资产决策</h4><p>我这两年确实晋升了，但整体变化不大。我偶尔找身边一些朋友聊天，挺现实的一个现象，在一二线大厂待了多年的老兵，确实有人能拿到百万年薪，听起来就还挺人生赢家的。但如果是三线及以下的厂子，除非做到高管，否则就算熬成技术老兵，年薪天花板可能也就小几十万，像普通程序员月薪3-5万基本也就到头了（排除最近走AI和量化这些线路的大聪明们）。光靠工资的话，收入其实挺有限的，再算上房贷、房租、一家老小的生活/娱乐/教育开销、说走不太敢走的旅行、各种人情往来，包括还有一些历史大件的债务偿还，每个月能攒下的钱真的不多，甚至大部分时候还在倒贴。所以，我所在的温饱圈子里面，大家聊得最多的还是怎么开源节流，要么想办法搞点副业，要么精打细算的过日子，反正不能让现金流太难看，加上如今的行情，总之是需要勒紧裤腰带过日子的。在资产持有方面我始终是典型的保守派，如果持有的资产（主要是房子）持续带来负现金流，那我坚持的原则就是果断抛售掉，减少负担，而我爱人基于对资产的情感以及对机会的博弈考量，跟我持相反意见，总之我们在这方面达不成共识，那有没有折中方案？是不是应该定一个止损点？我不知道，我猜谁也不知道。</p><h4 id="3、社交思维局限"><a href="#3、社交思维局限" class="headerlink" title="3、社交思维局限"></a>3、社交思维局限</h4><p>我最近几年的想法，对于我个人而言，就是尽最大可能，只见挚友、至亲，一是因为工作和家庭已经占尽了我大部分时间精力，二是源于原生家庭对我的影响（以家人为主）和我作为典型理工男长期形成的。当然，爱人同样跟我存在社交思维的差异，一是源于原生家庭对她的影响（酒桌文化、熟人社会），二是现阶段孩子成长对社交的需要，所以她认为拥有同频的人、真诚的情感、新的认知，在她生命中具有难能可贵的价值和意义。我能理解，我也支持，但我做不到。</p><h4 id="4、人工智能在改变很多东西"><a href="#4、人工智能在改变很多东西" class="headerlink" title="4、人工智能在改变很多东西"></a>4、人工智能在改变很多东西</h4><p><img src="https://www.panshenlian.com/images/post/live/2025/october/ai-001.jpg"></p><p>随着 AI 应用的不断涌现，加上原先就快节奏的工作生活方式，如今的我已经失去了仔细阅读一本书的耐心，我习惯了通过 ChatGPT 或 Deepseek 快速获取碎片信息，并进行加工整合，最终好像就能形成结论，很大程度上 AI 提供了极为高效的知识快餐，但长期以往，我担心自己会像吸毒品一样对 AI 产生不可逆的依赖，AI 让人更聪明，却让人更浮躁；让知识更近，却也让思考更远，让人逐渐失去对一个知识体系的系统化沉淀，并最终丧失独立思考的能力，我不知道会不会是这样，但是我明显发现整个社会的节奏都在变得更快！人们也被要求更快！另一个角度就是大家都在变聪明，依托 AI 的能力，我作为程序员能编写更高质量的代码，从而提升我的工作产出；爱人作为内容创作者，能更加精准高效的输出内容，从而留出更多的思考时间；孩子作为小学生，能更好玩更全面的完成作业，从而提升思维和想象能力。</p><p>工具即进化，人类从钻木取火中已习得工具的重要性，而AI，是我们这代人不得不登上的方舟。我们必须使用 AI，无论是工作还是学习，请一定使用 AI，AI 大概率比你身边大部分人都要见多识广、更加智慧，而且只会越来越厉害。</p><h4 id="5、健康焦虑"><a href="#5、健康焦虑" class="headerlink" title="5、健康焦虑"></a>5、健康焦虑</h4><p>自从三月份那次感冒之后，咳嗽就一直断断续续没停过，拖了整整三个月，加上身边不少亲友的身体也陆续亮红灯，我开始怀疑自己的身体是不是也藏着什么大问题，于是整个二、三季度都在跑医院做检查，先在港大做了全身体检，接着又将有异常的细项跑多家医院复查验证。结果呢？既不算完全健康，也没到大病预警的程度。但不管如何，需要开始重视身体了，对身体精打细算起来，运动优先​，作息需要科学合理安排，饮食也尽可能低糖低油，当然还有一个核心参数，那就是​​心态，毕竟已经过了拼身体的年纪，需要接受身体维修的事实。 </p><p>还是那句话，我们 90 后这代人，起码要工作到 65 岁，放平心态，细水长流，打持久战。</p><h4 id="6、开始做饭"><a href="#6、开始做饭" class="headerlink" title="6、开始做饭"></a>6、开始做饭</h4><p>我很自豪地说，我几乎不再吃外卖了，厨房成了我的第三居所，也是我们家庭难得的慢车道。</p><p>另外码农与厨农、写代码与做饭真的很像，从规划、设计、执行、调试，再最终交付一个明确的东西，每次都进行复盘，每次都接收反馈，然后在下一次进行迭代优化，两者都需要高度专注并接收即时反馈，我很享受写代码和做饭的过程，都能让我进入心流，都能能让我从焦虑中暂时抽离。</p><h2 id="对孩子"><a href="#对孩子" class="headerlink" title="对孩子"></a>对孩子</h2><p>你现在七岁了，九月份刚刚过完小学一年级进入二年级。对你来说，每天更关心的可能是放学后能不能多玩一会儿电子游戏、多踢一会儿足球，或者周末能不能去玩真人 CS、能不能跟着妈妈到处游玩。但你知道吗？不出意外的话，你跟大部分同龄人一样，七年后都会成为中考大军中的一员，并接受人生中第一次正式的挑战。听起来还很远，但成长在焦虑中的我们，已经需要在学科上为你提前规划，有意无意地给你安排英语听说读写、奥数、语文课外阅读书单…生怕你在某个环节掉队。</p><p><img src="https://www.panshenlian.com/images/post/live/2025/october/education-001.png"></p><p>当然，我们特别不希望你的人生像一个提线木偶，也不希望我们之间以后是靠成绩维持着爱和关系，但我们希望你的人生中能拥有更多选择权，我们第一次当父母，也因此还在思考哪些是对的，哪些是错的，我们会尽可能正确的“为你好”。</p><h4 id="1、小学二年级了"><a href="#1、小学二年级了" class="headerlink" title="1、小学二年级了"></a>1、小学二年级了</h4><p>一年级，你很幸运，如愿上了理想的小学、入选第一批少先队员、结交了很多要好的小朋友、妈妈很有耐心。<br>二年级，你更有热情，魔方成绩一直在突破、国际象棋和书法进步很快、还入选了足球校队。</p><p><img src="https://www.panshenlian.com/images/post/live/2025/october/education-002.jpg"></p><h4 id="2、不知道如何与你相处"><a href="#2、不知道如何与你相处" class="headerlink" title="2、不知道如何与你相处"></a>2、不知道如何与你相处</h4><p>妈妈对你特别包容，但在我看来过于松弛，或许再严格一点，你能够更加独立，或者更加“优秀”。</p><p>其实我们那个年代是一路打着长大的，我小时候过于任性、不听话，身上时常是紫一道红一道，但反正此刻我是理解父母的，也不妨碍我爱自己的父母。</p><p>所以现在，有时候跟你讲道理讲不通的时候，我还是会用一些“老办法”教育你，有时候是暴力沟通，有时候会上手打一顿，但是你属于吃软不吃硬的性格，即便每次被我打，也是不低头，搞得我每次打完，还得跟你道歉，但是你忘记得特别快，跟我很快又合好了。</p><h4 id="3、你一些特别的行为"><a href="#3、你一些特别的行为" class="headerlink" title="3、你一些特别的行为"></a>3、你一些特别的行为</h4><p>你每天听音频的时间太长了：小说、故事、英语磨耳朵、笑话…你的听瘾很大，工作日起码2小时，周末最少8小时，我很担忧你的听力，但是从你口中突然蹦出来一些让我惊讶的成语、一些奇怪又真实存在的知识、一些让人笑掉牙或醍醐灌顶的脑筋急转弯…又让我不得不接受，听也是一种输入方式。</p><h4 id="4、教育方式"><a href="#4、教育方式" class="headerlink" title="4、教育方式"></a>4、教育方式</h4><p>我有幸辅导过你几次作业，所以你有过几段惨痛的经历，我对过程没有耐心，又特别看重结果，所以你写作业的时候，如果出现书写不工整、态度不严谨、坐姿不规范、简单题目做错…那你可能就要挨揍了，我知道这些对于小孩子而言很正常，但是我没法处理太好，所以我决定不参与你的作业辅导了，你妈妈很有耐心，包揽了你所有的作业辅导工作，当然你也喜欢她的辅导，所以挺好的，我省得发脾气。</p><p><img src="https://www.panshenlian.com/images/post/live/2025/october/education-003.jpg"></p><h2 id="对爱人"><a href="#对爱人" class="headerlink" title="对爱人"></a>对爱人</h2><h4 id="1、自律"><a href="#1、自律" class="headerlink" title="1、自律"></a>1、自律</h4><p>你坚持早起、读书、创作、瑜伽…回报你的，是认知提升了，身体活力了，最重要的是与自己更加和解了，当然，你的小红书小绿书也越来越好了。</p><p>我始终相信，一个能在生活中自律、在心中留白的伴侣，是家庭最稳的底盘，也是维系家庭的根轴，让家在前进与旋转中不至于失衡。</p><h4 id="2、盼头"><a href="#2、盼头" class="headerlink" title="2、盼头"></a>2、盼头</h4><p>当初我们决定来深圳，并不是因为向往深圳，而是为了逃离郑州。</p><p>如今咱俩认知达成一致，35岁到55岁，是我们此生相对主控、自由的20年，是我们第一次、也是最后一次同时手握四张主动牌：</p><ul><li>生理</li><li>认知</li><li>资源</li><li>价值</li></ul><p>所以我们要节奏、节能与平衡，该放弃的尽早砍断，该坚持的全力聚焦。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p><img src="https://www.panshenlian.com/images/post/live/2025/october/home-001.jpg"></p><p>感谢我们都如此努力，也感谢国庆和中秋这个长假，给我留一点记录的缝隙。</p><p>我们的人生，就像在疾驰的双轨上各自奔跑，又被那颗旋转的陀螺牵系重心，不断向前，持续旋转。</p><p>只是在这中场休息哨声未响之前，我们何时能将这疾驰的双轨，融合成一条从容的慢车道？这恐怕是我们接下来即将面对的、最不确定的挑战。</p><p><img src="https://www.panshenlian.com/images/post/live/2025/october/home-002.jpg"></p><p>中秋没回家，月是故乡明。</p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/live/2025/october/family-001.png"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;&lt;a href="#前言" class="headerlink" tit</description></item><item><title>职场中的那些事（一）：热尔韦原则、呆伯特原则、彼得原则</title><category>职场</category><category>办公室</category><category>热尔韦原则</category><category>呆伯特原则</category><category>彼得原则</category><pubDate>Sun, 24 Nov 2024 09:21:03 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2024/11/24/career-review-001-2024/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/career/2024/11/career-001.jpg"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>今日读到一篇关于职场现象的文章，竟写于十五年前，解惑了我的一些职场迷思，也坚定了我的凡事皆有方法论，很有意思，在此分享中英版内容。</p><h2 id="中文版"><a href="#中文版" class="headerlink" title="中文版"></a>中文版</h2><p><a href="https://ouranswers.zhubai.love/posts/2471686289448792064/">《职场中的权力游戏：热尔韦原则》</a></p><h2 id="英文版"><a href="#英文版" class="headerlink" title="英文版"></a>英文版</h2><p><a href="https://www.ribbonfarm.com/2009/10/07/the-gervais-principle-or-the-office-according-to-the-office/">《The Gervais Principle, Or The Office According to “The Office”》</a></p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/career/2024/11/career-001.jpg"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;&lt;a href="#前言" class="headerlink" title=</description></item><item><title>二〇二四第二季：一家三口的新长征</title><category>生活</category><category>深圳</category><category>灏灏</category><category>人生伴侣</category><pubDate>Sun, 5 May 2024 05:04:03 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2024/05/05/live-001-may-day-in-2024/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/live/2024/may/family-001.jpg"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>在患得患失的南迁中逐渐明朗，我们一家三口逃离郑州，来到深圳安家落户，这一年的变迁，趁着假期的尾巴，我想大致描绘。之前心中的不安与未知已逐渐消散，取而代之的是愈发坚韧的信念和理性的思考。展望未来，我们满怀希望，迎接新的挑战和机遇。</p><h2 id="关于自己"><a href="#关于自己" class="headerlink" title="关于自己"></a>关于自己</h2><p>1、职业</p><p>我不确定，如果我继续留在职场，人生中还会经历几家公司。但不管如何，目前很值得庆幸，<a href="https://www.oceanpayment.com/">OceanPayment</a> 是一家能群策群力干实事的企业，不说企业的方向与规划，主要谈人和事在管理上的感受，流程标准化，规范框架化，机制透明化，执行落地协作高效，人员能力重培养，梯队文化重建设，有师可拜，有艺可学，所以在可预知的三五年内，作为基层管理岗，我会持续去解读岗位角色，把分内的事做极致，当然如果在支付领域，业务与技术能小有所成，我会更加开心。</p><p>2、收入</p><p>毫无疑问，大部分人来深圳都是为了搞钱，我当然也不例外。那这一年有搞到钱了吗？答案是：有，但好像又没有。之所以有，是相较于之前在郑州的收入，确实多了不少；而之所以又没有，是指收入减掉支出之后的结余，不多。但是在深圳我深刻体会到手艺人不愁没饭吃，知识和能力给人以安全感，所以就且积极向上，碌碌有为。程序员或说技术人员工资过了三万/月，基本就锁定天花板了，所以一定要规划自己的第二曲线，当然前提肯定是要把主业干好，否则只会顾此失彼。</p><p>3、社交</p><p>尽最大可能，只见挚友、至亲。随着年纪增长，除了工作和家庭，余下给我的时间真的少之又少，更别提到诸如玩游戏、刷短视频此类荒废虚度人生的事。</p><p>4、学习</p><p>碎片化学习真的会让人产生积累知识的错觉，如果时间允许，请一定去系统地、持续地去学习，每天至少要留出1个小时的时间来学习，年龄越大请越加重视。由于精力有限，我可能不会再花大把时间去专研某个技术细节的实现，但偶尔还是会看一些优秀的开源项目代码，并且把更多的学习时间聚焦到技术架构和业务架构上。</p><p>5、健康</p><p>颈椎与眼睛。自初入职场，我就时刻提醒自己勤能补拙，所以经常在电脑前工作学习一坐就是十个小时，后来不出意外，颈椎杵废了，眼睛看废了。</p><p>膝盖。两年前我读了村上春树的 <a href="/2022/05/18/weekly-7/">《当我谈跑步时，我谈些什么》</a> ，之后我便开始每天跑步，从每天1km、2km，到每天5km，周末10km，后来不出意外，膝盖磨废了。</p><p>酒精。我是酒精不耐受体质，但几乎在所有酒桌上，我都毫不抗拒饮酒，每次都是喝得全身通红才肯作罢，现在想起觉得大可不必，未来应该会坚守原则，不近酒精。</p><p>所以，请对自己的身体精打细算，科学合理安排，尽可能早睡午休素食晚餐，不要过度透支身体，避免造成不可逆的损伤，并可能为之付出更多的时间去修复。</p><p>90后这代人，肯定是要工作到65岁的，熬肯定是熬不过年轻人，所以也不要去拼工作时长了，请记得要细水长流，打持久战。</p><h2 id="关于孩子"><a href="#关于孩子" class="headerlink" title="关于孩子"></a>关于孩子</h2><p>自灏灏出生之后，我的人生引入了一个核心变量，时刻左右着我的许多决策，但所幸我的人生本来也没有什么大规划，反而驱使我开始思考一些人生走向的问题。</p><p><img src="https://www.panshenlian.com/images/post/live/2024/may/school-001.jpg"></p><p>1、幼儿园与插班生</p><p>幼儿园是孩子步入社会的第一战场，格外重要。你在郑州上了近三年幼儿园，结识了一群很要好的玩伴，也有幸遇到一群真心爱护孩子的老师，你特别幸运，我很推荐这所幼儿园（郑州管城区柏菲幼儿园）。所以去年来深圳面临转学插班问题时，我和你母亲确实焦虑，担心幼儿园的教育理念差异、师资水平素质差异、小朋友的玩圈差异等等给你带来的额外心理精神负担，以致于后来我们只能祈祷你会被友好相待，并尽快适应融入，如今一年过去，你确实适应很快，只不过适应的结果不尽理想，由于这边的幼儿园管教方式相对严厉，所以你与老师之间的相处，多了些敬畏与拘谨，少了些情感与天真，中途我考虑过转校，但又担心你可能还会遇见更坏的情况，加之成本考虑，最终不了了之，你坚持至今，也即将毕业，不算太坏。</p><p>我依然希望每个孩子，在人生中都能遇到和蔼可亲、谆谆教诲的老师，也希望每位老师都能因材施教，温润孩子的心灵。</p><p>九月份即将升学，在一年前我们就在能力范围内帮你准备了两所小学，幸运的是，今年多了一所名校供你报名，所以我们当然会优先报名名校，至于能否被录取，我们就随遇而安吧，至少你已经有两所小学兜底。</p><p>2、体育运动</p><p>你对体育运动项目没有任何抗拒，所以从足球、篮球、乒乓球、羽毛球、跳绳、体能、跆拳道、武术等等都尝试了一遍，我从体格上看得出你是力量型的选手，但由于精力原因，我们经过沟通只保留足球这一项运动进行每周的高频训练，其余运动项只好根据平时的空闲时间和即兴程度灵活安排，你不是天赋异禀，所以需要持续训练，我把家里的客厅打造成小足球训练场，以此来增多你的足球训练时间，我希望你在初中之前保持热爱，坚持锻炼，让身体变得强壮健硕一些，如此一来你在应试教育下能更加抗击打。</p><p><img src="https://www.panshenlian.com/images/post/live/2024/may/son-001.jpg"></p><p>3、棋牌游戏</p><p>棋牌类游戏，你很狂热，从国际象棋、围棋、中国象棋、陆战旗、斗兽棋、飞行棋等等，你都爱不释手，每天盯着我，一有时间就与我一决胜负，由于我棋艺不精，怕耽误你在棋牌类游戏上的成长，所以我一边陪练，一边在某宝上购买适合少儿学习的动画教学视频，一整套云盘教程只需要几元钱，性价比很高，让你每天自己选择性的看，我也跟着学了很多专业术语，例如国际象棋中的“王车易位”、围棋里的“气、目、提子”等等。</p><p>4、识字发音</p><p>我的普通话讲得很差，一股很重的潮汕口音，拼音这块只能完全靠你妈妈教，我希望你在上小学之前能达到自主阅读，一来是提升你自主吸收知识的效率，二来也能让爸爸妈妈从中释放出来。另外你依然有一些后鼻音发音较为不标准，但不影响你认字阅读，你喜欢 <strong>洪恩识字</strong>、<strong>洪恩拼音</strong>，这款 APP 寓乐于学，包括认、练、说、玩等方面，你很有兴趣，也能吸收。</p><p>5、英语</p><p>在我的整个求学生涯中，英语一直是我的短板，且耗费了我许多时间精力，在可预见的将来，你也是要跟应试教育死磕英语，所以你尽可能提前接触，跟从专业英语老师掌握科学的学习方法，例如自然拼读等，而不是像我一样死记硬背局限式的学习英语，背了又忘，忘了又背，条件允许的情况下，我会争取给你找外教，或许在小学毕业前你的英语能够正常听说，当然我和你妈妈也计划陪跑英语。</p><p>6、编程</p><p>从游戏陪练到语音助手，你已经知道 AI 大致是个什么东西了，上小学之后，我可以开始试着带你接触编程，如果你感兴趣，或许我们可以结对学习，参加编程比赛，如果你兴趣不大，那我也不勉强。</p><h2 id="关于爱人"><a href="#关于爱人" class="headerlink" title="关于爱人"></a>关于爱人</h2><p>1、坚定不移的家庭支撑者</p><p>一年前你是南迁深圳的坚定支持者，我说孟母三迁，你这是第一迁。如今你是我和孩子日常起居饮食学习生活的照料者，正如我在四年前某天早晨描述的一样：“她骨子里的贤惠，像一块玉。”</p><p><img src="https://www.panshenlian.com/images/post/live/2024/may/lover-001.jpg"></p><p>2、依然追求事业上的成就感</p><p>我一直鼓励你做一个自媒体人，时间自由，精神自由，但又一直打击批评你的作品，最近你的作品终于有起色，命中了流量密码，这像是反手给了我一个大耳光，我应该一边闭嘴，一边默默支持你。</p><p><img src="https://www.panshenlian.com/images/post/live/2024/may/lover-002.jpg"></p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/live/2024/may/family-001.jpg"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;&lt;a href="#前言" class="headerlink" title="</description></item><item><title>二〇二三第一季：涅槃重生</title><category>生活</category><pubDate>Sun, 12 Mar 2023 04:07:23 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2023/03/12/live-001-march-day-in-2023/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/live/2023/march/shenzhennorthstation.jpg"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>极为多事的二〇二二，终于彻底离我远去。这一年，我的心态经历了过山车般的起伏——数十年不遇的疫情席卷，房地产行业惨遭脚踝斩，建业集团大步裁员潮，孩子手术的压迫感，家庭债台高筑…——这一切，使我不得不进行一次空前的思考，我们急需开启一段新的可期之旅。</p><h2 id="二〇二三年一月七日"><a href="#二〇二三年一月七日" class="headerlink" title="二〇二三年一月七日"></a>二〇二三年一月七日</h2><p>难以想象，我们在郑州已生活七年之久，客观上，房子在哪，家就在哪，工作于此，生活于此，大人有熟悉的老友，孩子有熟悉的玩伴，毫不掩饰地说，我们依赖这座城市。</p><p>但并非不愿意留下，归根到底多半是留不下。重压调控下行业不可逆，重创挤压下企业不乐观，这座城市，短时间很难，扎根于此，未来不可期，或者说，我耗不起。</p><p>北京，上海，深圳 … </p><p>教育，医疗，包容度，宜居性，发展性，成本 …</p><p>彼时的我们综合看来，除了深圳没有更好的选择，于是我和爱人思来想去，最终计划在春节前到深圳考察几天。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shenzhen-001.jpg"></p><p>说是来深圳考察，实际上内心早已想好，匆匆几天，我们并没有寻找到更多前往这座城市的理由，只不过是在渐进式地说服自己——来都来了。</p><h2 id="二〇二三年一月廿二日"><a href="#二〇二三年一月廿二日" class="headerlink" title="二〇二三年一月廿二日"></a>二〇二三年一月廿二日</h2><p>春节，回了一趟 “娘家” ，除夕酒过三巡，回顾过往，长辈们自然是对年轻一代予以鼓励认可，再过三巡，便是寄与憧憬期待。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/newyear-001.jpg"></p><p>除夕餐桌上，似乎大家都提了想提的事，聊了想聊的天，但发生在过去一年365天里的那些耕耘、收获、拼搏、欢畅、感慨、思考，我想大部分都只回荡在自己脑海中。</p><h2 id="二〇二三年一月廿九日"><a href="#二〇二三年一月廿九日" class="headerlink" title="二〇二三年一月廿九日"></a>二〇二三年一月廿九日</h2><p>春节我休了假期，特意多陪家人两天，每个人都有故乡，尽管可能凋敝贫瘠、支离破碎，但却总在某些不经意的时刻令我们魂牵梦绕，黯然泪下。</p><p>关心过彼此，再见可能又是下个春节。我们匆匆启程返郑，错开高峰，出奇的返程荒。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/backhome-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/backhome-002.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/backhome-003.jpg"></p><p>很欣喜，这小家伙如今已 “足够” 独立，自己吃饭，自己穿衣，自己跟爷爷奶奶聊天，自己睡觉，自己解锁我的 ipad … 说是一旦上了小学，他们就能完全脱离家长，这可算是一个让人 “伤心” 的事实。</p><h2 id="二〇二三年二月三日"><a href="#二〇二三年二月三日" class="headerlink" title="二〇二三年二月三日"></a>二〇二三年二月三日</h2><p>年后的持续不景气，加剧了我的危机感和恐惧感。我干脆快刀斩乱麻，眼看争取被裁员这件事行不通，虽可惜了我的 n+1 ，但是没办法，我还是只能主动离开。</p><p>再见，新生活。</p><p>开启，新生活。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/resignation-001.jpg"></p><h2 id="二〇二三年三月五日"><a href="#二〇二三年三月五日" class="headerlink" title="二〇二三年三月五日"></a>二〇二三年三月五日</h2><p>主观上承认，有人养确实是世界上最幸福的事。毫不夸张，我提前享受了一个月的 “退休” 生活。</p><ul><li>睡到自然醒</li><li>吃垃圾食品</li><li>每天狂刷剧</li><li>做全身检查</li></ul><p>浑（wu）浑（bi）噩（man）噩（zu）过了半个月，恰巧准备去上海一趟，想起来孩子的新年愿望是去 <strong>迪士尼</strong> ，于是安排了这么一个行程。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-010.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-002.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-003.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-004.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-005.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-006.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-007.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-008.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shanghai-009.jpg"></p><p>再见，上海。</p><h2 id="二〇二三年三月十一日"><a href="#二〇二三年三月十一日" class="headerlink" title="二〇二三年三月十一日"></a>二〇二三年三月十一日</h2><p>三月六日到达深圳，落了住处，休息调整，在七日便开始寻找机会。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/shenzhennorthstation.jpg"></p><p>三月九日，我内心基本确定了一家匹配企业，作为自己来深的奋斗起点，平台年轻，有竞争力，有潜力，我也年轻。</p><p>我轻度思考过，此次转速换挡，很大概率上没有对错之分，所以我只需要把时间精力，专注于新环境，<strong>适应，发展，改造</strong>，我一直都察悟到自身无数的知识盲区和能力的局限性，但在深圳强者如林的环境中，我能感受到新的成长可能性。</p><p><img src="https://www.panshenlian.com/images/post/live/2023/march/running-001.jpg"></p><p>三月十一日，深圳第一跑，我选择在深圳人才公园，简单热身之后，便开始起跑，周末逛公园的人很多，跑者也多，跑道站满了游客，跑步中途经常中断，我想，在这里晨跑应该会畅快许多。</p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>我基本不喝咖啡，但是每到一座城市，我都习惯找星巴克，差不多舒缓的音乐，差不多的嘈杂，差不多的写作聊天氛围，差不多的放松，一下就能融进这陌生。</p><p>回忆了近两小时，再见，过去，你好，新生活。</p><p>感谢老东家：成功的路上并不拥挤，因为坚持下来的人不多。只要你能经得起诱惑，受得了挫折，忍得住委屈，耐得住时间，守得住方向，则无事不成。</p><p>感谢新东家：天道酬勤，厚德载物。</p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/live/2023/march/shenzhennorthstation.jpg"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;&lt;a href="#前言" class="headerl</description></item><item><title>竹白百科月度汇报 × 邀请您加入阅享室！</title><category>竹白</category><category>竹白百科</category><category>汇报</category><pubDate>Thu, 12 Jan 2023 01:20:18 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2023/01/12/001-zhubai-wiki-monthly-report/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<h2 id="月度汇报"><a href="#月度汇报" class="headerlink" title="月度汇报"></a>月度汇报</h2><p>各位好，<a href="https://www.zhubai.wiki/">竹白百科</a> 自 22 年 12 月初上线运营至今，已一月有余，现简单汇报：</p><p><img src="/images/post/product/zhubai_monthly_report/monthly_report_01.png" alt="monthly_report1"> </p><p><strong>访客总览：</strong> 粗略有 3000+ 用户访问，PV 在 8000+ ，互动数近 20000。</p><p><img src="/images/post/product/zhubai_monthly_report/monthly_report_02.png" alt="monthly_report2"></p><p><strong>流量趋势：</strong> 整体数据稍有抖动，经部分高流量网站收录后，会陡增流量，平常日均用户访问量在 100 左右。</p><p><img src="/images/post/product/zhubai_monthly_report/monthly_report_03.png" alt="monthly_report3"> </p><p><strong>访客来源：</strong> 基本 TOP3 地区是：北京、上海、深圳。非常正常，不做解释。</p><p><img src="/images/post/product/zhubai_monthly_report/monthly_report_04.png" alt="monthly_report4"> </p><p><strong>导航喜好：</strong> 原计划是希望找出用户目标喜好，我好区分主次，但目前来看，分布高低有序，恐怕跟位置的排列先后有特别大的关系，基本次序就是：先锋作品、先锋创作者、新锐作品、新锐创作者、活跃创作者、日创作者、周更创作者、新收录、优秀博客。</p><p><img src="/images/post/product/zhubai_monthly_report/monthly_report_05.png" alt="monthly_report5"> </p><p><strong>互动分布：</strong> 海报分享和内容排序，在互动中占比 90+% ，而用户主动提交收录和 RSS 复制的占比很小，总收录在 100 以内，更多的收录是从其它数据源获取。</p><p><strong>当然，</strong> 透过分析可以看到一些不为人知的渠道源和有趣的用户行为，更多数据分析，如果各位感兴趣可以移步：<a href="http://analy.zhubai.wiki/">竹白百科-访迹</a>。</p><h2 id="未来展望"><a href="#未来展望" class="headerlink" title="未来展望"></a>未来展望</h2><p> <a href="https://www.zhubai.wiki/">竹白百科</a> 会一如既往专注优质创作者与内容，规划上暂定 <strong>四忌</strong>：</p><ul><li>忌商业化</li><li>忌背离用户</li><li>忌急于求成</li><li>忌虎头蛇尾</li></ul><p>另外，为了更好地发掘闪闪发光的创作者与内容，我创建了 <strong>微信群</strong>（竹白百科 × 阅享室）：</p><ul><li>阅：阅读相关</li><li>享：分享、共享，甚至参与共创共建</li><li>室：微信群形式，固定交流地</li></ul><p>在阅享室，我们可以聊一切与阅读和创作相关的话题。同时由于 <a href="https://www.zhubai.wiki/">竹白百科</a> 现阶段还是一个嗷嗷待哺的小 Baby，所以一边需要我 “细心呵护”，另一边更需要各位 “长辈” 多提宝贵建议与高质反馈，竹白百科小 Baby 才能快速成长为我们希望的样子。</p><p><strong>特别是，</strong> <a href="https://www.zhubai.wiki/">竹白百科</a> 虽起于 <a href="https://zhubai.love/">竹白</a>，但绝不能限于 <a href="https://zhubai.love/">竹白</a>，需要包容和接纳一切优质创作者与内容，不忘初心。</p><h2 id="末尾"><a href="#末尾" class="headerlink" title="末尾"></a>末尾</h2><p><strong>最后，微信我，表明来意，我们阅享室见！</strong></p><p><img src="/images/post/product/zhubai_monthly_report/monthly_report_06.jpg" alt="monthly_report6"> </p>]]></content:encoded><description>&lt;h2 id="月度汇报"&gt;&lt;a href="#月度汇报" class="headerlink" title="月度汇报"&gt;&lt;/a&gt;月度汇报&lt;/h2&gt;&lt;p&gt;各位好，&lt;a href="https://www.zhubai.wiki/"&gt;竹白百科&lt;/a&gt; 自 22 年 12 月初上线</description></item><item><title>为什么我又做了竹白百科？</title><category>实验</category><category>竹白</category><pubDate>Sat, 3 Dec 2022 12:24:18 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/12/03/trial-002-zhubai-wiki/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>一直以来，我在信息获取方面，基本有三个渠道：Github，RSS，Email 。隔三差五，我会通过这三个渠道去消化新增的信息源。</p><ul><li><p><a href="https://github.com/">Github</a> , 作为技术爱好者，也是相关从业者，自然是非常熟悉，最近发现国内的开源慢慢成长起来，但依然需要时间。</p></li><li><p>RSS , 我使用的客户端是 <a href="https://feedly.com/">feedly</a> ，不一定是最好的，但目前适合我，并且经过几年取舍，留存下来的这一批技术类、艺术类以及新闻类的信息源，基本质量都很高，至少是我希望了解的内容。 </p></li><li><p>Email , 依然有很多优质的信息源或站点，弥补 RSS 的空缺。</p></li></ul><p>互联网是张神奇的网，只要你一直在网上，大部分高质量，或者说有趣的人和事，最终都会传递到你这个点。</p><p>所以 <a href="https://zhubai.love/">竹白</a>，就是我从 <a href="https://geekplux.com/">GeekPlux</a> 了解到的。</p><h2 id="初心"><a href="#初心" class="headerlink" title="初心"></a>初心</h2><p>作为一名信息源收集狂，对于高质量的内容，我自然是爱不释手，但由于 <a href="https://zhubai.love/">竹白平台</a> 的一些定位原因，之后我做了一个小工具弥补了部分遗憾，我在之前有具体聊过，详细见 <a href="/2022/08/07/trial-001-zhubai/">《竹白网站实现专栏与文章检索》</a>。</p><h2 id="回声"><a href="#回声" class="headerlink" title="回声"></a>回声</h2><p>在竹白检索小工具诞生之后，我有意发了一些群，和部分站点，作为宣传，希望对他人也有所用。</p><p>后面陆续收到不少反馈，总体上大家认为工具好用，但存在不足，这些声音，我都心中有数，可惜精力有限。</p><h2 id="升级"><a href="#升级" class="headerlink" title="升级"></a>升级</h2><p>恰巧国庆之后，我居家办公长达一月有余，通勤的时间减少了，企业业务减缓了，自然我的可支配时长增加了，所以开始琢磨着，要不把之前的竹白小工具做一个升级。于是基本做了几件事：</p><h4 id="1、域名"><a href="#1、域名" class="headerlink" title="1、域名"></a>1、域名</h4><p>申请了竹白百科独立域名 <a href="https://www.zhubai.wiki/">zhubai.wiki</a>，之前把竹白检索工具集成在我的某篇博文中，自然不合适，主要也是不方便使用，所以申请了一个独立域名，好记，也比较纯粹，当然还是为了便利性。</p><h4 id="2、数据"><a href="#2、数据" class="headerlink" title="2、数据"></a>2、数据</h4><p>内容新增了竹白作品展示，之前只有创作者榜单，和文章检索，但是不够直接，所以我索引把竹白创作者和作品，都一一作了展示，并且雨露均沾。</p><p>另外对于一些创作维度，个人角度做了一些统计分析，目前包括：</p><ul><li>先锋作品</li><li>先锋创作者</li><li>新锐作品</li><li>新锐创作者</li><li>活跃创作者</li><li>日更创作者</li><li>周更创作者</li><li>新收录</li></ul><p>统计依据和数据含义，<a href="https://www.zhubai.wiki/">竹白百科</a> 上我做了详细说明。</p><p><img src="/images/post/product/zhubai/article.jpg" alt="article"></p><p><img src="/images/post/product/zhubai/author.jpg" alt="author"></p><ul><li>检索结果</li></ul><p><img src="/images/post/product/zhubai/search-box.jpg" alt="search-box"></p><p><img src="/images/post/product/zhubai/search-result.jpg" alt="search-result"></p><h4 id="3、UI"><a href="#3、UI" class="headerlink" title="3、UI"></a>3、UI</h4><p>我是全栈工程师，但如果要说 UI ，那真的是难倒我了，于是鉴于自己一直收听的播客节目 <a href="https://www.xiaoyuzhoufm.com/episode/62d58b0664f141ad8150151f">《枫言枫语》</a> 之前也做了一件类似的事 <a href="https://xyzrank.com/">《中文播客榜》</a>，所以我直接把 UI 样式搬了过来，然后用 React 搭建了几个组件，基本就用了。</p><h4 id="4、分享"><a href="#4、分享" class="headerlink" title="4、分享"></a>4、分享</h4><p>初心一如从前，做竹白百科的目的，也是为了让用户发现竹白的好内容，独乐乐不如众乐乐。以往分享的形式，是链接形式，于是我写了一个分享插件，希望能够让大家以图片海报的形式，在朋友圈或网上，更好的分享传阅，把竹白的好内容和优秀创作者，分享给更多人。</p><ul><li>支持作品分享</li><li>支持创作者分享</li></ul><p><img src="/images/post/product/zhubai/share.jpg" alt="share"></p><p><img src="/images/post/product/zhubai/sharecard.jpg" alt="sharecard"></p><h4 id="5、访迹"><a href="#5、访迹" class="headerlink" title="5、访迹"></a>5、访迹</h4><p>另外，添加了一个简单的访问痕迹统计页面 - <a href="http://analy.zhubai.wiki/">竹白访迹</a>，方便分析竹白百科的流量模型和用户行为，也提供了流量的来源渠道，方便大家 <strong>反向</strong> 查看更多有意思的信息源。</p><ul><li>访客总览</li></ul><p><img src="/images/post/product/zhubai/analy1.jpg" alt="analy1"></p><ul><li>流量趋势</li></ul><p><img src="/images/post/product/zhubai/analy2.jpg" alt="analy1"></p><ul><li>访客来源</li></ul><p><img src="/images/post/product/zhubai/analy3.jpg" alt="analy1"></p><ul><li>用户轨迹</li></ul><p><img src="/images/post/product/zhubai/analy4.jpg" alt="analy1"></p><h2 id="末尾"><a href="#末尾" class="headerlink" title="末尾"></a>末尾</h2><p>更多功能，建议到 <a href="https://www.zhubai.wiki/">竹白百科</a>  or <a href="http://analy.zhubai.wiki/">竹白访迹</a> 上细细查阅，如有问题，请随时联系我。</p>]]></content:encoded><description>&lt;h2 id="背景"&gt;&lt;a href="#背景" class="headerlink" title="背景"&gt;&lt;/a&gt;背景&lt;/h2&gt;&lt;p&gt;一直以来，我在信息获取方面，基本有三个渠道：Github，RSS，Email 。隔三差五，我会通过这三个渠道去消化新增的信息源。&lt;/p&gt;
&lt;</description></item><item><title>程序员的可能性-读《黑客与画家》</title><category>黑客</category><category>画家</category><pubDate>Sat, 13 Aug 2022 09:55:11 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/08/13/book-002-hackers-and-painters/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="/images/post/book/02-hackers-and-painters.jpg"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>《黑客与画家》（Hackers and Painters - Big Ideas from the Computer Age）一书于 2011 年出版，作者为硅谷创业之父保罗·格雷厄姆（Paul Graham），全书内容基本是作者的文集汇整，也是他前半生写照。</p><p>全书主要介绍黑客爱好与动机，又细谈黑客成长、黑客对世界的贡献、编程语言与黑客工作方法，以及涉教育、技术、管理、道德乃至创业与致富的认知，不一而是。</p><p>从许多不同方面解释这个计算机时代的内在脉络，揭示其中发展轨迹，方便我们看清现状与未来的方向。</p><p>虽时隔十年之久才翻阅，但仍深受启发。保罗在其前言中写道：“<strong>我们生活中的一切，都正在成为计算机</strong>”。人们曾经认定这个时代应该是太空时代或原子时代，而如今计算机对人类生活的影响远超其它，特别是在本书出版十年之后，我们再审视周围之一切，确也如此。</p><p>当然，本书行文源自作者博客，故内容不连贯、断续且跳跃，实属正常，我在此仅是顺书意序，约略总结。</p><h2 id="黑客的成长与待世观点"><a href="#黑客的成长与待世观点" class="headerlink" title="黑客的成长与待世观点"></a>黑客的成长与待世观点</h2><h3 id="受关注与合群"><a href="#受关注与合群" class="headerlink" title="受关注与合群"></a>受关注与合群</h3><p>许多青少年，希望自己合群、受关注，因此想方设法。譬如他们会关注服饰、考虑如何结盟、与其他受欢迎的人变得关系密切、甚至吸烟打架、欺负同学、忤逆父母，诸如此类。此阶段对于这部分群体而言，智力无足轻重，智力的分量远远不如相貌、魅力和运动能力的分量重。</p><p>在美国的中学生群体中，在塑造个人魅力方面，则更加孜孜不倦、精益求精，一个美国的青少年在醒着的每一分钟，都在琢磨怎样才能更受欢迎，一年 365 天，天天如此。</p><p>文艺复兴时期代表人物阿尔伯蒂有一句名言：“<strong>任何一种艺术，不管是否重要，如果你想要在该领域出类拔萃，就必须全身心投入。</strong>”</p><p>许多高智商的人，他们在学校里都被称为 “<strong>书呆子</strong>” ，跟大部分青少年一样，书呆子也想让自己合群、受欢迎，但相较这些，他们的脑子更愿意装着另外的事情—— <strong>让自己变得更聪明</strong> 或 <strong>完成一些伟大的事</strong> ，例如设计奇妙的火箭、写出漂亮的文章、理解编程原理等等，他们分心去干别的事情，没把心思放在研究合群与如何受到关注，因此书呆子往往不受欢迎。</p><p><strong>书呆子追求智力，并全身心投入，代价是饱尝不受欢迎之苦。</strong></p><p>比尔 · 盖茨不善于社交是出了名的，但是他发现了正确的答案，至少从收入上看是如此。</p><p>许多黑客，特质也是如此。</p><h3 id="黑客与画家"><a href="#黑客与画家" class="headerlink" title="黑客与画家"></a>黑客与画家</h3><p>在许多人印象中，计算机是冷冰冰的、精确的、井然有序的，而画画是某种原始欲望热烈狂放的表达方式。</p><p>这种看法是错的，计算机与画画有许多共同之处，黑客与画家都是 <strong>创作者</strong>，与作曲家、建筑师、作家一样，黑客与画家都是试图创作出优秀的作品，他们本质上都不是在做研究。</p><p>创作者创造优秀作品，他们创造优秀作品的方式往往不是从头做起，而是在现有成果的基础上做出一些小小的调整，或者将已有的观点用比较新的方式组合起来。这种类型的工作很难用 <strong>研究性</strong> 的论文表达，而计算机与画画，都是创造优秀作品的方式，例如客户不需要从头创造编程语言、协议、算法、数据结构等等，只需站在巨人肩膀之上、基于优秀作品之上去进行创作。</p><p>画家学习绘画的方法主要是动手去画，而黑客学习编程的方法也理应如此，一种直白的学习途径是直接学习优秀程序的源代码。</p><p>无数古代油画放在 X 光下检视，就能看出修改痕迹，四肢的位置被移动过，或者脸部的表情经过了调整… 可以发现通常一幅画开始通过一张草图，然后再逐步填入细节，如果发现构想是错的，那你就必须重新动手修改，而黑客的工作也应该如此，不能期望现有一个完美的规格设计，然后再动手编程，这样想是不现实的。如果你预先承认规设计是不完美的，在编程之时，就可以根据需要随时修改规格，最终会有一个更好的结果。</p><p>画家使用油画颜料，黑客使用好的编程语言，本质都是选择好工具。</p><h3 id="好奇心与质疑"><a href="#好奇心与质疑" class="headerlink" title="好奇心与质疑"></a>好奇心与质疑</h3><p>翻开老照片，看到以前的穿着时尚，你感到难为情。查看过去很多观点，如今发现是一个笑话。</p><p>为何彼时没被察觉怀疑？可能由于时空差异，可能人们被主流思想所禁锢，可能是人们担心自己观点会被别人称为异端邪说…</p><p>这些龌龊的、见不得人的过去与观点，这些挡住去路的石头，需要秉持纯粹好奇心与永远质疑的态度，去一探究竟，拨开迷雾。</p><p>优秀作品往往来自其他人忽视的想法，而最被忽视的想法就是那些被禁锢的思想观点，智力越高的人，越愿意去思考那些惊世骇俗的思想观点。<strong>不要让自己成为人群的一分子，而要尽可能地远离人群，观察正在发生的事情，特别注意那些被压制的思想观点，保持清晰地思考。</strong> 但是走得越远，你的处境就会越困难，受到的阻力也会越大，因为你没有迎合社会习俗，而是一步步地与它背道而驰。小时候，每个人都会鼓励你不断成长，变成一心智成熟、不再耍小孩子脾气的人。但是，<strong>很少有人鼓励继续成长，变成一个怀疑和抵制社会错误潮流的人。</strong></p><blockquote><p>如果自己就是潮水的一部分，怎么能看见潮流的方向呢？</p></blockquote><h3 id="黑客特质"><a href="#黑客特质" class="headerlink" title="黑客特质"></a>黑客特质</h3><p>在大众眼里，黑客（hacker）就是入侵计算机的人。可是，在程序员眼里，黑客指的是优秀程序员。他们不仅能力精通，而且有许多 <strong>良好的坏习惯</strong>。</p><ul><li>黑客做事不符常规，正常人往往会使用寻常做法，而他们会诞生许多不符常规的聪明做法。</li><li>黑客不服从管教、思想自由，如果总是服从管教，按部就班，那他们也就无法成为优秀程序员，无法逾越很多障碍，无法做技术创新。</li></ul><h2 id="黑客的成果与对世影响"><a href="#黑客的成果与对世影响" class="headerlink" title="黑客的成果与对世影响"></a>黑客的成果与对世影响</h2><h3 id="人月神话"><a href="#人月神话" class="headerlink" title="人月神话"></a>人月神话</h3><p>《人月神话》（The Mythical Man-Month）是布鲁克斯所写的一本软件项目管理名著。所谓“人月”就是 <strong>一个人在一个月内所能完成的工作量。</strong> 假如某个项目预估需要 12 个人月，那么派 4 个人处理这个项目，理论上需要 3 个月，派 6 个人则只需要 2 个月。但是，布鲁克斯认为这种换算机制在软件业行不通，是一个神话，因为软件项目是 <strong>交互关系复杂</strong> 的工作，需要大量的 <strong>沟通成本</strong>，人力的增加会使沟通成本急剧上升，反而无法达到缩短工期的目的。随着项目中的人数越来越多，开会讨论各个部分如何协同工作所需的时间越来越长，无法预见的互相影响越多越大，产生的 bug 也越来越多。</p><blockquote><p>在本质上，软件项目的人力与工期是无法互换的，但项目进度落后时，光靠增加人力到该项目中，并不会加快进度，反而有可能使进度更加延后。</p></blockquote><h3 id="创业公司的经济优势"><a href="#创业公司的经济优势" class="headerlink" title="创业公司的经济优势"></a>创业公司的经济优势</h3><p>由于互联网软件的程序员非常辛苦，所以会使得经济优势根本性地从大公司向创业公司转移。互联网软件要求的那种工作强度和付出，只有当公司是其本人所有时，程序员才愿意提供。<strong>软件公司可以雇到能干的人，让他们去干轻松的事情，也可以雇到不能干的人，让他们去干艰苦的事情，但是无法雇到非常能干的人，让他们去干非常艰苦的事情。</strong> 因为互联网软件的创业不需要太多的资本，所以大公司可以与创业公司竞争的优势就所剩无几了。</p><p>E. B. 怀特曾经从一个农民朋友那里听到一则趣闻。许多农场用电篱笆防止奶牛逃跑，但是不少电篱笆其实并没有通电。不过奶牛们已经吃过苦头，显然学会了不去碰电篱笆，这时不通电也能起到效果。“奶牛们，行动吧！” 他写道，“趁着统治者打鼾时，夺回你们的自由！”</p><p>如果你是一个黑客，并且梦想自己创业，可能会有两件事情令你望而却步，不敢真正开始采取行动。</p><p><strong>一件是你不懂管理企业，另一件是你害怕竞争。</strong> 可是实际上，这两件事都是没有通电的电篱笆。</p><p>对于管理企业，你只需要记住两点：做出用户喜欢的产品，保证开支小于收入。<br>对于害怕竞争，也只需要记住两点：大企业生产效率之低，反而是你的执行力与优势之高。</p><h3 id="如何创造财富"><a href="#如何创造财富" class="headerlink" title="如何创造财富"></a>如何创造财富</h3><p>如果你想致富，最好的办法就是自己创业，或者加入创业公司。几百年来，这一直是致富的可靠途径。</p><p><strong>守恒原则</strong></p><p>从经济学观点看，可以把创业想象成一个压缩过程，你的所有工作年份被压缩成了短短几年。你不再是低强度地工作四十年，而是以极限强度工作四年。特别在高技术领域，这种压缩的回报尤其丰厚，工作效率越高，额外报酬就越高。当然你需要承受短期极限困苦与压力。</p><p><strong>价值原则</strong></p><p>快速致富的方法有许多种，比如赌博、投机、婚姻、继承、偷窃、敲诈、垄断、行贿、游说、造假、开矿等，但是我们谈论的致富方式是 <strong>通过创造有价值的东西在市场上获得回报</strong>，通过创造有价值的东西而致富，这种方法的优势不仅仅在于它是合法的，还在于它更简单，你只需要做出别人需要的东西就可以了。创造用户之所需，是创造财富的一个重要指导。</p><p><strong>事实</strong></p><p>关于通过创业创造财富，我们除了接受以上两个原则之外，我们还需要承认另外两点事实：运气与潜规则。</p><ul><li>运气</li></ul><p>任何创业公司的成功历程中，运气都是一个很大的随机因素，大至国际氛围、经济市场、政策环境，小至一次日常决策。</p><ul><li>潜规则</li></ul><p>潜一，付出是未知量，你无法决定到底付出多少，可能你觉得自己应该更勤奋工作 2 到 3 倍，从而就能得到相应的回报。但是当你真正创业以后，往往是你的竞争对手决定了你到底要有多少辛苦，而且他们做出的决定都是一样的：你能吃多少苦，我们就能吃多少苦。</p><p>潜二，创业的付出与回报虽然总体上是成比例的，但是在个体上是不成比例的。就像蚊子，作为一个物种，他们的数量极多，但作为个体，却极难生存。</p><p><strong>困难模式</strong></p><p>基于以上原则与事实，然后你决定了要创业，在商业价值相当的多种创业方案中，建议你选择更加困难的那一个，选择一个高门槛的领域，甚至是一个难以复制的模式，以便你未来建立壁垒（例如申请专利），表面上你是在建立防守栅栏，实质上是反守为攻。</p><p><strong>最大化致富</strong></p><p>创业公司并不只是过去二三十年发生在硅谷的事情。如今，通过创造财富而致富已经成为了普遍的模式。每一个这样做的人差不多都应用了同样的诀窍：可测量性和可放大性。前者来自小团队和合作，后者来自开发新技术。这也是最大化致富需要具备的两样东西。</p><p>工厂流水线工人的报酬是按照计件制计算的，工人们只有可测量性、没有可放大性，所以不可能致富。<br>在大企业团体中，个人的表现往往无法单独测量，优秀的人会被平均化，被整体拖累，所以不可能致富。</p><p>然而，任何一个通过自身努力而致富的个人，在他们身上应该都能同时发现可测量性和可放大性。例如 CEO 、影视明星、专业运动员、基金经理等，当然他们的收入和风险是对称共存的，他们的头上都悬着一把宝剑，随时可能掉下来，一旦他们搞砸了，他们也就完了。</p><p>所以，整体上如果你有一个令你感到安全稳定的工作，你是不会致富的，因为没有危险，就几乎等于没有可放大性。</p><p>如果你想同时具备可测量性和可放大性，不一定非得成为 CEO、影视明星、专业远动员或者基金经理不可，你只需要成为某个攻克难题的小团体的一部分就可以了。攻克难题往往采取某种新思维模式或高科技技术手段，小团体则天生就适合解决技术难题。技术的发展是非常快的，今天很有价值的技术，几年后可能就会丧失价值。小团队在如今这个时代可谓如鱼得水，因为他们不受官僚主义和繁琐管理制度的拖累。而且，技术的突破往往来自非常规的方法，小团队就较少受到常规方法的约束。当然大公司也能开发出新技术，只不过开发得比较慢而已，这是大公司的毛病。</p><p>小团队 = 可测量性<br>高科技 = 可放大性</p><h3 id="品味与优秀的设计"><a href="#品味与优秀的设计" class="headerlink" title="品味与优秀的设计"></a>品味与优秀的设计</h3><p>“哥白尼不认同托勒密的体系，一个极其重要的原因是，他觉得托勒密提出的偏心等距点（equant）毫无美感…”<br>———— 托马斯 · 库恩，《哥白尼革命》</p><p>“美感是第一道关卡。丑陋的数学在世界上无法生存。”<br>———— G.H.哈代，一个数学家的辩白》</p><p>一提到 “品味”，很多人会对你说 “品味是主观的” 、“品味没有好坏之分”，然而事实并非如此，品味是有好坏美陋之分的，通过不断见长，品味会出现变化，逐步提高。一旦你走出狭隘的自我，开始学习思考，你就会发现，主观品味无时无刻不发生变化，众多不同学科对 “美” 的认识有着惊人的相似度，优秀设计的原则是许多学科的共同原则，一再反复地出现，简列 14 个优秀之设计原则。</p><ol><li>好设计是简单的设计</li><li>好设计师永不过时的设计</li><li>好设计是解决主要问题的设计</li><li>设计是启发性的设计</li><li>好设计通常是有点趣味性的设计</li><li>好设计是艰苦的设计</li><li>好设计是看似容易的设计</li><li>好设计是对称的设计</li><li>好设计是模仿大自然的设计</li><li>好设计是一种再设计</li><li>好设计是能够复制的设计</li><li>好设计常常是奇特的设计</li><li>好设计是成批出现的</li><li>好设计常常是大胆的设计</li></ol><p>实际上，我觉得发现丑陋的东西要比想象出一个优美的东西更容易。大多数做出优美成果的人好像只是为了修正他们眼中丑陋的东西。伟大成果的出现常常来源于某人看到一样东西后，心想我能做得比这更好。拜占庭帝国的《圣母像》最早是根据某个公认的模板画的，非常机械呆板。几百年后的 14 世纪，意大利画家乔托看到以后，深感不满，决定动手改进，他因此成为文艺复兴的先行者。哥白尼对地心说无法解释的事情深感困扰，他的同时代人都觉得这可以忍受，他却认为一定能找到一种更好的解释。</p><p>单单是无法容忍丑陋的东西还不够，只有对这个领域非常熟悉，你才可能发现哪些地方可以动手改进。你必须锻炼自己。只有在成为某个领域的专家之后，你才会听到心里有一个细微的声音说：“这样解决太糟糕了！一定有更好的选择。” 不要忽视这种声音，要培育它们。</p><p>优秀的秘诀就是：<strong>非常严格的品味，再加上实现这种品味的能力</strong>。  </p><h2 id="附录"><a href="#附录" class="headerlink" title="附录"></a>附录</h2><p>附录提取关于保罗·格雷厄姆之创业哲学、黑客价值观。</p><h3 id="创业公式"><a href="#创业公式" class="headerlink" title="创业公式"></a>创业公式</h3><ol><li>搭建原型</li><li>上线运营（别管 Bug）</li><li>收集反馈</li><li>调整产品</li><li>成长壮大</li></ol><h3 id="黑客伦理"><a href="#黑客伦理" class="headerlink" title="黑客伦理"></a>黑客伦理</h3><ol><li>使用计算机以及所有有助于了解这个世界本质的事物都不应受到任何限制。任何事情都应该亲手尝试。</li></ol><p>（Access to computer——and anything that might teach you something about the way the world works——should be unlimited and total. Always yield to the Hands-On Imperative!）</p><ol start="2"><li>所有信息应该都是自由的。</li></ol><p>（All information should be fee.）</p><ol start="3"><li>不信任权威，提倡去中心化。</li></ol><p>（Mistrust Authority——Promote Decentralization.）</p><ol start="4"><li>判断一名黑客的水平应该看他的技术能力，而不是看他的学历、年龄或地位等其他标准。</li></ol><p>（Hackers should be judged by their hacking, not bogus criteria such as degrees,race,or position.）</p><ol start="5"><li>你可以用计算机创造美和艺术。</li></ol><p>（You can create art and beauty on a computer.）</p><ol start="6"><li>计算机使生活更美好。</li></ol><p>（Computers can change your life for the better.）</p><p>根据这六条 “黑客伦理” ，黑客价值观的核心原则可以概括成这样几点：</p><ul><li>分享</li><li>开放</li><li>民主</li><li>计算机的自由使用</li><li>进步</li></ul><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="/images/post/book/02-hackers-and-painters.jpg"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;&lt;a href="#前言" class="headerlink" title="前言"&gt;&lt;/a&gt;前言&lt;/h2&gt;&lt;p&gt;《黑客与画</description></item><item><title>竹白网站实现专栏与文章检索</title><category>实验</category><category>竹白</category><pubDate>Sun, 7 Aug 2022 09:11:18 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/08/07/trial-001-zhubai/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<link href="https://www.panshenlian.com/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"><script type="text/javascript" src="https://www.panshenlian.com/npm/vue@2/dist/vue.js" ></script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>👏👏👏 欢迎来访朋友，本竹白功能已经升级为 <a href="https://www.zhubai.wiki/">《竹白百科》</a>，增加了作品展示，分享，RSS订阅等，也会陆续把现有文章搜索等功能迁移过去，同时兼容移动端，阅读作品很方便，具体升级原因详见 <a href="/2022/12/03/trial-002-zhubai-wiki/">《为什么我又做了竹白百科？》</a></p><p>当然，如果大家喜欢当前版本，那么请继续使用，此页面功能依然会为您保留。</p><h2 id="竹白初测评"><a href="#竹白初测评" class="headerlink" title="竹白初测评"></a>竹白初测评</h2><p><a href="https://zhubai.love/">竹白</a> 这个平台算是较新的创作者平台，能够帮助创作者触达更多紧密且忠实的读者，我挺喜欢它的 UI 设计和体验，目前我在竹白平台也跟风开了一个专栏 <a href="https://wednesday.zhubai.love/">《无聊科技正经事》</a>，我猜测一是质量一般，所以读者寥寥，二是我没有主动去自我推广（当然推广之后可能仍然没有流量）。</p><p>竹白平台目前没有用户流量优势，需要创作者自我引流，目前靠口碑传播，算是一个小众产品。</p><p>目前我发现竹白平台有三个小遗憾，我觉得可以有。</p><ol><li><strong>第一是创作者专栏主页的【主色调】，或许可以做成智能配色。</strong></li></ol><p>用户是很感官的，一套配色很能彰显一位创作者的风格。</p><p>而目前创作者专栏主要通过手工配色、或者联系官方同学协助设置主色调（我猜的），我们可以实现智能配色。智能配色可以基于头像，头像挺能代表一个人。</p><ol start="2"><li><strong>第二是难以发掘创作者。</strong></li></ol><p>竹白的创作者中，不乏高品质专栏，我起初试图去发现、或订阅更多我未知的创作者，但是我找不到途径。</p><ol start="3"><li><strong>第三是难以发掘优质内容。</strong></li></ol><p>当然，内容目前也不支持检索。</p><p>基于三点粗糙的想法，我自己简单做了实现，补偿遗憾，体现在以下《竹白创作榜》和《竹白文章检索》。</p><h2 id="竹白创作榜"><a href="#竹白创作榜" class="headerlink" title="竹白创作榜"></a>竹白创作榜</h2><div id="zhubai-rand"></div><h2 id="竹白文章检索"><a href="#竹白文章检索" class="headerlink" title="竹白文章检索"></a>竹白文章检索</h2><div id="zhubai-post-search"></div><h2 id="2022-08-18-更新"><a href="#2022-08-18-更新" class="headerlink" title="2022-08-18 更新"></a>2022-08-18 更新</h2><p>本文最近有不少大佬分享：“觉得有意思，发现了很多有趣的创作者”。</p><p>也收到不少读者的邮件和评论夸赞，认为实用。</p><p>在此我表示感谢，也确实受宠若惊。</p><p>另外，也有部分同学提出意见，例如 “创作者排行版无意义”。</p><p>我在此也简单回复，本次排行版效果确实基于仅有的数据做出尽可能的排行行为，存在很多不合理，所以充其量作为一种展示形式。</p><p><strong>如果大家有更好的建议，特别欢迎提出</strong>，我们一起优化，争取把更优质的工具提供给大家。</p>]]></content:encoded><description>&lt;link href="https://www.panshenlian.com/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" crossorigin="anonymous"&gt;
&lt;script ty</description></item><item><title>《暗时间》中的学习习惯与核心竞争力</title><category>读书</category><category>时间</category><pubDate>Fri, 1 Jul 2022 14:49:08 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/07/01/book-001-dark-time/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="/images/post/book/01-darktime.jpg" alt="《暗时间》中的学习习惯与核心竞争力"></p><p>初看《暗时间》，以为是一本探讨时间聚焦的书籍。细读之后方才发现，暗时间的提法只是全书的起点，而后作者通过亲身经历和自己总结的大量专业领域知识，跨界结合自身对心理学、认知科学以及数学的研究，对思维、学习、工作、时间、习惯等方面进行了深度思考和深入探讨。本文摘抄《暗时间》，主要片段系关于学习思考、时间效率、阅读方法以及个人核心竞争力。</p><h2 id="学习与思考"><a href="#学习与思考" class="headerlink" title="学习与思考"></a>学习与思考</h2><ol><li><p><strong>Google 与 Wikipedia。</strong> 遇到问题最先想到的，也是学习某样东西做功课（homework）最先用到的工具。</p></li><li><p><strong>看书挑剔，只看经典。</strong> 如何选择经典，可以到网上做做功课，看看评价，综合分析一下。书籍是我们知识的主要来源，<strong>在选择书籍的时候做足功课是对我们自己的时间负责。</strong> 这和在超市里买东西时对比各个品牌是一个道理，只不过奇怪的是，我很少见到有人对书籍像买衣服那样精挑细选的。</p></li><li><p><strong>做读书笔记。</strong> 一是将自己阅读时候的思考（包括闪念）总结下来，而是将书中的好例子摘抄下来。有了 Evernote 之类的工具来管理笔记，非常便于回顾，加深理解。<strong>很多时候，仅仅是用自己的语言重新表述一下就能够极大地加深印象和理解。</strong> 我觉得，人与人学习的差距不在资质上，而在花在思考的时间和思考的深度上（后两者常常也是相关的）。<strong>获得多少并不取决于读了多少，而取决于思考了多少、多深。</strong></p></li><li><p><strong>提到思考，我有一个小习惯。</strong> 利用走路和吃饭的时候思考，还有睡觉前必然要弄一个问题放在脑子里，在思考中迷糊入睡。发现这样一来往往在不知不觉中多出来大量的思考时间。</p></li><li><p><strong>多看心理学与思维的书，因为它们是跨学科的。</strong> 知识分两种，一是我们通常所谓的知识，即领域知识。而是关于我们的大脑吸收知识的机制的知识，后者不妨称为元知识。虽说这也是领域知识，但跟其他领域知识不同的是，它指导着我们学习其他所有的领域知识。</p></li><li><p><strong>学习一项知识，必须问自己三个重要问题：</strong> </p></li></ol><ul><li>1）、它的本质是什么</li><li>2）、它的第一原则是什么</li><li>3）、它的知识结构是怎样的</li></ul><ol start="7"><li><strong>学习和思考的过程中常问自己的几个问题：</strong></li></ol><ul><li>1）、你的问题到底是什么？（提醒自己思考不要偏离问题。）</li><li>2）、OK，到现在为止，我到底有了什么收获呢？（提醒自己时不时去总结，整理学习的东西）。</li><li>3）、设想自己正在将东西讲给别人听（有声思考；能否讲出来是判断是否真正理解的最佳办法）。</li><li>4）、设想需要讲给一个不懂的人听。（迫使自己去挖掘知识背后最本质、往往也是最简单的解释）。</li><li>5）、时常反省和注意自己的思维过程。尤其是当遇到无法理解或解决的问题之后，最需要将原先的思维过程回顾一遍，看看到底哪个环节被阻塞住了妨碍了理解。问题到底出在哪里。并分析以后需要加强哪方面的思维习惯，才能够不在同样或类似的时候被绊住。对此，将思维的大致脉络写下来是一个很好的习惯。</li><li>6）、养成反驳自己的想法的习惯：在有一个想法的时候，习惯性地去反驳它，问自己“这个难道就一定成立吗？”、“有没有反例或例外？”、“果真如此吗？”之类的问题。（参见Critical Thinking）</li><li>7）、人的思维天生就是极易流于表面来理解事物的（参见《Psychology of Problem Solving》第11章）。觉得自己理解了一个问题了么？条件反射性地问自己：你真的理解了吗？你真的理解了问题的本质了？问题的本质到底是什么？目前我的理解是什么？我对这个理解感到满意吗？这样的理解到底有什么建设性呢？等等。</li></ul><h2 id="时间和效率"><a href="#时间和效率" class="headerlink" title="时间和效率"></a>时间和效率</h2><ol><li><p><strong>趁着对一件事情有热情的时候，一股脑儿把万事开头那个最难的阶段熬过去。</strong> 万事开头难，因为从不了解到了解基本的一些事实，是一个新知识暴涨的阶段，这个时候的困难是最大的。有人熬不过去，觉得困难太大就放弃了。不过，狂热的兴趣可以抵消对困难的感觉，所以趁着对一件事情有热情的时候，开一个好头是很重要的。（当然，这并不是说持之以恒就不重要了）。当然，也许这个是因人而异的，对我来说我会在对一件事情有浓厚兴趣的时候非常专注地学习，把很多 groundworks 做掉。后面就会顺利一些了。</p></li><li><p><strong>重要的事情优先</strong>（详见史蒂芬·柯维的《高效能人士的七个习惯》或《要事优先》）。尽量避免琐事骚扰，不重要的事情能不做就不做。有时候，紧急的事情往往只是当事人觉得必须马上做完才显得紧急或者干脆就是紧他人之急，最糟糕的就是纯属性格上原因觉得每件事情都得第一时间完成，很多看上去紧急的事情实际上并不是真的”不能再拖了”，有的干脆就并不需要或值得去做。有很多事情都是可以先放一放甚至完全let go的，否则的话就整天被所谓”紧急”的事情牵着鼻子走了。</p></li><li><p><strong>重要的事情营造比较大的时间块来完成。</strong> 比如一本好书，或者一个重要的知识点，最好不要切得太琐碎了看，否则看了后面忘了前面，不利于知识的组织与联系。</p></li><li><p><strong>同时也要善于利用小块时间。</strong> 也就是《奇特的一生》中所说的“时间下脚料”，如何利用前面有几个方法。同时，也善于创造整块时间（如通过要事优先）。</p></li><li><p><strong>重视知识的本质。</strong> 对于程序员来说这一点尤其重要，程序员行业的知识芜杂海量，而且总是在增长变化。很多人感叹跟不上新技术。应对这个问题的办法只能是：<strong>抓住不变量</strong>。大量的新技术其实只是一层皮，背后的支撑技术其实都是十来年不变的东西。<strong>底层知识永远都不过时</strong>。算法数据结构永远都不过时。基本的程序设计理论永远都不过时。良好的编码习惯永远都不过时。分析问题和解决问题的能力永远都不过时。强大的学习能力和旺盛的求知欲永远都不过时。你大脑的思维方式永远都不过时。</p></li><li><p><strong>重视提前积累的强大力量。</strong> 计划订长一点，自然就可以多获得准备的时间。设想你若干年后会在做什么事情，需要哪些技能，现在就开始准备。<strong>你比别人往后多看一年，你就比别人领先一年的时间来准备，这个差别是绝大的</strong>。一个5年计划便可以让你获得从现在开始的5年准备时间。5年中每天腾出半个到一个小时专心于某一件事情，认准一个方向，每次走一点，其实不要说5年，两年就会发现会起到宏大的效应。长期订阅我的Blog的朋友们也一定注意到我基本上不写东西，一般一个月写上2篇就算多的了。但总结一段时间的学习和思考的习惯却一直都没有停止（博客文章对我来说是学习和思考的副产品，我并不为写文章而写文章），所以5年下来竟也写了不少东西。所以这就是一个简单的例子。你大致还可以从我的Blog看出来我一段时间关注的东西，一般来说，一段比较长的时间（少则半年至一年——譬如对心理学与思维的关注；多则几年——譬如对编程技术的关注），在这段时间内，我的业余时间会被一个主题所充斥。反之，如果不知道目的是什么，就不知道往哪个方向上使劲，就容易产生无用功。</p></li><li><p><strong>抬起头来。</strong> 人的思维是非常容易只见树木不见森林的（否则这个成语从哪来的呢？）。时不时抬起头来审视一下自己正在做的事情，问一问它（对现在或未来）有什么价值，是不是你真正希望做的。你学到的东西到底是什么？它们重要吗？你需要在这个时候学习这些吗？（见第2条）。你的时间就是你的资源，你投入这些资源来掌握知识，所以到底用来掌握哪些知识是一个很重要的问题。仅仅遵循兴趣是不够的，人会对很多次要的东西产生兴趣，并一头钻进去浪费好多时间。所以判断一个东西值不值得学习是很重要的。</p></li><li><p><strong>退订RSS。</strong> RSS Reader 是个时间黑洞。就算 mark all as read，在有大量feed的情况下，也会无形中消耗掉大量的时间。我们一旦订阅了某个RSS之后就会倾向于不肯退订它，心想也许某天有个重要的信息会从那里得到。这其实是源于人不肯”关上一扇门（即便门内的收益概率极小）”的心理（参见《Predictably Irrational》）；而实际上，关上一扇门，有时能够增大收益期望。仔细观察一下reader里面的feeds，有哪些是真正有价值的，把那些没价值的或者价值很小乃至于不值得每天被它骚扰的，全都退订掉。不要舍不得，那些一个星期都没出现让你眼睛一亮的内容的feed，很大的可能是永远也不会出现。就算可能，也别担心你会漏掉什么宝贵信息，<strong>真正宝贵的信息，在其他来源你也会接触到的</strong>。一开始我的Greader里面每天都有大量的新内容，每天都是1000+，但一段时间之后发现除了信息焦虑，实际上有价值的内容不多，现在，我很高兴地发现自己摆脱了这种状况，我持续不断地退订feeds，留下的内容越来越少，也越来越精，带来的阅读焦虑也越来越少。（顺便推荐一个东西，aideRSS，初步使用，感觉对订阅reddit这样的每天更新大量内容的feed很有用）。</p></li><li><p><strong>有时间吗？总结总结最近得到的新知识吧。</strong> 一般来说，我在一段时间内学习的一些东西总是会在这段时间内一直在脑子里打转，一有时间空隙（譬如走路，吃饭）它们就会自己蹦出来，促使我去进一步思考和总结。永远不要认为对一个知识的把握足够深刻，“理解”的感觉很多时候只是假象。学会反问自己对知识到底把握了多少，是很有价值的。（如何反问，前面的总结中有提到）。</p></li><li><p><strong>有时间吗？看本书吧。</strong> （传统的）阅读和思考永远优于所谓的在互联网上汲取新知识，后者往往浅表、不系统、乃至根本没价值。</p></li><li><p><strong>制定简要的阅读计划。</strong> 选出最近认为对你最有价值的书，先总览一下，决定阅读的顺序（哪些章节可以优先阅读）。然后每天看一点。并利用走路、吃饭、乘车或其他不适合带着书和笔的时间来总结看过的内容，建立知识结构，抽取知识本质，与以往的大脑中的知识建立联系。（参见《奇特的一生》）</p></li></ol><h2 id="阅读方法"><a href="#阅读方法" class="headerlink" title="阅读方法"></a>阅读方法</h2><ol><li><p><strong>根据主题来查阅资料，而不是根据资料来查阅主题。</strong> 以前读书的时候是一本一本的读，眼里看到的是一本一本的书，现在则是一章、甚至一节一节的读，眼中看到的不是一本一本的书，而是一堆一堆的章节，一个一个的知识主题，按照主题来阅读，你会发现读的时候不再是老老实实地一本书看完看另一本，而是非常频繁地从一本书跳到另一本书，从一处资料跳到另一处资料，从而来获得多个不同的人对同一个主题是如何讲解的。比如最近我发现在看蒙特卡罗算法时就查了十来处资料，其中有三四篇 paper 和六七本书；这是因为即便是经典的书，你也不能指望它对其中每一个主题的介绍都是尽善尽美的，有些书对某个主题（知识点）的介绍比较到位，有些书则对另一些知识点介绍得比较到位。而有时候一篇紧凑的 paper 比一本书上讲得还要好。我硬盘里面的书按主题分类，每个主题下面都有一堆书，当我需要学习某个主题的知识时（譬如贝叶斯学习或者神经网络），我会把里面涉及这个主题的书都翻开来，索引到相关章节，然后挑讲得好的看。那么，如何判断一个资料是好资料还是坏资料呢？</p></li><li><p><strong>好资料，坏资料。</strong> 好资料的特点：从问题出发；重点介绍方法背后的理念（ rationale ），注重直观解释，而不是方法的技术细节；按照方法被发明的时间流程来介绍（先是遇到了什么什么问题，然后怎样分析，推理，最后发现目前所使用的方法）。坏资料的特点是好资料的反面：上来就讲方法细节，仿佛某方法是从天上掉下来的，他们往往这样写“我们定义… 我们称… 我们进行以下几个步骤… ”。根本不讲为什么要用这个方法，人们最初是因为面对什么问题才想到这个方法的，其间又是怎样才想出了这么个方法的，方法背后的直观思想又是什么。实际上一个方法如果将其最终最简洁的形式直接表达出来往往丢失掉了绝大多数信息，这个丢掉的信息就是问题解决背后的思维过程。至于为什么大多数书做不到这一点，我在这里试着分析过。</p></li><li><p><strong>学习一个东西之前，首先在大脑中积累充分的“疑惑感”。</strong> 即弄清面临的问题到底是什么，在浏览方法本身之前，最好先使劲问问自己能想到什么方法。一个公认的事实是，你对问题的疑惑越大，在之前做的自己的思考越多，当看到解答之后印象就越深刻。记得大学里面的课本总是瀑布式地把整个知识结构一览无余地放在面前，读的过程倒是挺爽，连连点头，读完了很快又忘掉了，为什么？因为没有带着疑问去学习。</p></li><li><p><strong>有选择地阅读。很多人觉得我读书速度很快，其实我只是有选择地阅读。</strong> 这里的选择体现在两个地方，一是选择一本书中感兴趣的章节优先阅读。二是对一本书中技术性较弱或信息密度较低的部分快速地略读。一般来说，除了技术性非常强的书之外，大多数书的信息密度很低，有很多废话。一般来说在阅读的时候应该这样来切分内容：</p></li></ol><ul><li>1、问题是什么？</li><li>2、方案是什么？</li><li>3、例子是什么？</li></ul><p>如果是需要解释一个现象的（譬如《黑天鹅》），那么</p><ul><li>1、现象是什么？</li><li>2、解释是什么？</li><li>3、支撑这个解释的理由是什么？</li><li>4、例子是什么？</li></ul><p>一般来说，这一二三四用不了多少字就可以写完了（如果假设只举一到两个精到的例子的话），这样的无废话著作的典型是《合作的进化》；那为什么有些书，明明核心观点就那点东西（顶多加上几个精要的例子罢了）却写得长得要命呢？因为人的思维都有一个“联想”的特点，写着写着就容易旁逸斜出，而且作者自己也往往觉得引申出去挺牛逼，有时候很多与主题无关的废话就掺和进来了；那么，阅读的时候就应该有选择性地滤掉这些不相干的废话；此外还有一种可能性就是大量冗余的例子。一般来说组织得比较好的书会有详细且一目了然的目录和索引，根据目录首先就可以滤掉一部分（比如某个子章节的内容你以前是看过的），然后有时候作者还会举很多冗余的例子，如果你已经觉得印象够深刻了这些例子完全可以不看（一些书就非常厚道地对每个观点只辅以一两个最最经典的例子，譬如《与众不同的心理学——如何正视心理学》，这样的书我最是喜欢）。</p><ol start="5"><li><p><strong>阅读的分类。我一般把书分为两类，一类是知识的。一类是思维的。</strong>（当然，还有第三类，就是娱乐的，不作讨论:-) ）<br>一般来说我更倾向于阅读培养思维的，譬如《你的灯亮着吗？》、《决策与判断》、《别做正常的傻瓜》、《How to Think Straight about Psychology》、《数学与猜想》、《Ask the Right Questions》…… <strong>因为思维方面的东西是跨学科的，任何时候都用得上。并且，反之如果思维没有培养好的话，学习东西也容易走错方向或者事倍功半。</strong> 当然，话说回来，光看思维方面的书，不去选择一门或几门领域知识，也是思而不学则殆。所以这里所谓“更倾向于”是指时间分配方面的。</p></li><li><p><strong>利用时间碎片：任何一点时间都可以用于阅读。</strong></p></li><li><p><strong>为什么看不懂？</strong> 如果看不懂一个知识，一般有如下几个可能的原因：</p></li></ol><ul><li>1）、<strong>你看得不够使劲。</strong> 对此古人总结过——书读百遍其义自现。虽然这个规律不是任何时候都成立的，但是从认知科学的角度看是完全可以解释的，我们在阅读的时候，注意力往往会有选择性地关注其中的某一些“点”，而忽略了另一些“点”，于是一遍看下来可能因为某一些忽略导致无法理解整体。或者干脆看的时候就没注意其中一些细节但重要的东西。此外，大脑理解一个东西需要一定的处理时间，人脑的处理速度很慢，神经冲动每秒传输速度不过百米，所以不能指望看到哪懂到哪。最后，我们可能因为思维定势的原因会从某个特定的角度去看一句话而忽略了从不同角度去理解的可能性。对于这类情况，仔仔细细地再多读两遍，多试着去理解两遍，往往会“哦！原来这样。”地恍然大悟。</li><li>2）、<strong>其中涉及到了你不懂的概念。</strong> 这是技术性的不理解。这种情况就需要 Cross Reference 。如果一句话中用到了你不懂的概念，那就去查，现在很多书都是电子书，直接搜索一下，或者，对于纸书，看一下书后面的索引就行了。奇怪的是很多人看不懂也不分析一下为什么不懂，就直接放弃了。正如解决问题一样，问题卡住解决不了，第一时间要做的就是分析到底为什么解决不了，而不是直接求救。</li><li>3）、<strong>作者讲述的顺序不对。</strong> 你接着往下看，也许看到后面就明白了前面的了。</li></ul><ol start="8"><li><strong>如何在阅读之前就能获得对一本书质量的大致评估。</strong> 在深入阅读之前能够迅速评估一本书的质量可以节省很多时间。基本上有几个线索：</li></ol><ul><li>1）、看作者。牛作者写的书一般都不错。</li><li>2）、看目录和简介。一份好的目录和简介能够透露这本书质量的相当一部分信息。目录结构是否清晰，是否直白（而不是装神弄鬼），都是衡量的线索。</li><li>3）、看 Amazon 上的评价，这里要注意的是，除了看整体打分之外，更要看打分最低的人是怎么说的，因为小众意见往往有可能来自那些真正懂行的人（除了来踢馆的），如果在打分最低的意见里面看不到真正有价值的反驳意见的话就相当肯定书是不错的了。</li><li>4）、看样章。Amazon 上一般都可以随机浏览一些章节的，表达是否清晰，论证是否严谨，内容是否深刻，基本是几页纸就能看出来的。</li></ul><ol start="9"><li><strong>如何搜寻到好书。</strong> 几个线索：</li></ol><ul><li>1）、同作者的著作。</li><li>2）、Amazon 相关推荐和主题相关的书列（类似豆瓣的豆列）。</li><li>3）、一本好的著作（或一份好的资料——不管是书还是网页）在参考资料里面重点提到的其他著作。</li><li>4）、有时对于一个主题，可以搜索到好心人总结的参考资源导引，那是最好不过的。</li></ul><h2 id="知识结构"><a href="#知识结构" class="headerlink" title="知识结构"></a>知识结构</h2><ol><li><strong>抓住不变量。</strong> </li></ol><p>我喜欢把知识分为本质的和非本质的。对于前者采取提前深入掌握牢靠的办法，对于后者采取待用到时查手册的方法。</p><p>如何区分本质的和非本质的知识想必绝大多数时候大家心里都有数，我举几个例子：对程序员来说，硬件体系结构是本质的，操作系统的一些重要的实现机制是本质的，主流编程范式（OO、FP）是为了满足什么需求出现的（出现是为了解决什么问题），是怎么解决的，自身又引入了哪些新的问题，从而适用哪些场景）。这些我认为都是本质的。我想补充一点的是，并不是说硬件体系结构就要了解到逻辑门、晶体管层面才行（其实要了解到这个层面代价也很小，一两本好书就行了），也并不是说就要通读《Computer Architecture: Quantitative Approach》（中译名《计算机系统结构：一种定量的方法》）才行。而是关键要了解那些重要的思想（很长时间不变的东西），而不是很细的技术细节（易变的东西）。《Computer Systems: A Programmer’s Perspective》（中译名《深入理解计算机系统》）就是为此目的，针对程序员的需求总结出那些本质知识的好书。</p><p>再来说一下为什么需要预先牢靠掌握这些本质的知识：</p><ul><li><p>1）、根据Joel Spolsky同学的说法（原文），编程语言技术是对底层设备的封装，然而封装总是会出现漏洞的，于是程序员被迫下到“下水道”当中去解决问题，一旦往下走，漂亮的OO、N层抽象就不复存在了，这时候不具备坚硬的底层知识就会无法解决问题。简而言之就是这些底层知识会无可避免的需要用到，既然肯定会被用到那还是预先掌握的好，否则一来用到的时候再查是来不及的，因为本质的知识也往往正是那些需要较长时间消化掌握的东西，不像Ruby的mixin或closure这种翻一下manual就能掌握的东西。（英语也是这样的本质知识——上次在PyCN上看到一个招Python开发人员的帖子将英语列为必备技能，却并不将自然语言处理列为必备技能，正是因为英语不是可以临阵磨枪的东西，而且作为知识的主要载体，任何时候都少不了它，如果不具备英语能力，这个就会成为个人知识结构的短板或瓶颈，而且由于需要长时间才能获得这项能力，所以这个瓶颈将持续很长时间存在。我们曾经在 <a href="https://groups.google.com/g/pongba">TopLanguage</a> 上讨论过如何花最少的时间掌握英语）另一方面，在问题解决当中，如果不具备必要的知识，是根本无从思考的，再好的分析能力也并不是每个问题都能分析出该用哪些知识然后再去查手册的，很多时候是在工具和问题之间比较，联想，试探性的拼凑来解决问题；这就使得一个好的既有知识基变得至关重要。（实际上以上这个是一个较大的话题，希望有一天我能够把它详细展开说清:)）</p></li><li><p>2）、如果你不知道某个工具的存在，遇到问题的时候是很难想到需要使用这么样一个工具的，本质 knowldge就是使用最为广泛的工具，编程当中遇到某些问题之后，如果缺乏底层知识，你甚至都不知道需要去补充哪些底层知识才能解决这个问题。</p></li><li><p>3）、你必须首先熟悉你的工具，才能有效地使用它（须知工具的强是无敌的，但这一切得以“了解你的工具”为前提，甚至得以“了解目前可能有哪些工具适合你的问题”为前提）。一门语言，你必须了解它的适用场景，不适用场景（比如继承能解决你的问题不代表继承就是解决你的问题的最适合的方案，须知问题是一个复杂系统，解决方案总是常常引入新的问题）。你必须了解它支持的主要<strong>编程范式</strong>，此外你还必须了解它的traps和pitfalls（缺陷和陷阱，如果不知道陷阱的存在，掉进去也不知道怎么掉的。）这些都是本质知识，如果不事先掌握，指望用的时候查manual，是很浪费时间的，而且正如第2点所说，正因为你不知道这些知识（如适用场景），从而用sub-optimal的方式使用了一门语言自己可能还不知道（最小白的例子是，如果你不知道语言支持foreach，那么可能每次都要写一个冗长的循环，较常见的例子是不知道有很方便的库设施可以解决手头的问题所以傻乎乎的自己写了一堆代码），因为人的评价标准常常是：只要解决了最醒目的问题并且引入的新问题尚能忍受，就行。注意，熟悉并非指熟悉所有细节，而是那些重要的，或者无法在需要用到的时候按需查找的知识。比如上面提到的：<strong>适用场景不适用场景，编程范式，主要语言特性，缺陷和陷阱</strong>。</p></li></ul><p>当然，以上作为程序员的本质知识列表并不完备，关键是自己在学习新知识的时候带着第三只眼来敏锐地判断这个知识是否是不变量，或不易变的量，是否完全可以在用的时候查手册即可，还是需要提前掌握（一些判断方法在上文也有所提及）。并且学会在纷繁的知识中抽象出那些重要的，本质的，不变的东西。我在之前的part里面也提到我在学习新知识的时候常常问自己三个问题：该知识的（体系或层次）结构是什么、本质是什么、第一原则是什么。</p><p>另外还有一些我认为是本质知识的例子：分析问题解决问题的思维方法（这个东西很难读一两本书就掌握，需要很长时间的锻炼和反思）、判断与决策的方法（生活中需要进行判断与决策的地方远远多于我们的想象），波普尔曾经说过：All Life is Problem-Solving。而判断与决策又是其中最常见的一类Problem Solving。尽管生活中面临重大决策的时候并不多，但另一方面我们时时刻刻都在进行最重大的决策：如：决定自己的日常时间到底投入到什么地方去。如：你能想象有人宁可天天花时间剪报纸上的优惠券，却对于房价的1%的优惠无动于衷吗？（《别做正常的傻瓜》、《Predictably Irrational》）如：你知道为什么当手头股票的股价不可抑止地滑向深渊时我们却一边揪着头发一边愣是不肯撤出吗？（是的，我们适应远古时代的心理机制根本不适应金融市场。）糟糕的判断与决策令我们的生活变得糟糕，这还不是最关键的，最关键的是我们从来不会去质疑自己的判断，而是总是能“找到”其他为自己辩护的理由（《错不在我（Mistakes were made, but not by me）》）又，现在是一个信息泛滥的时代，于是另一个问题也出现：如何在海洋中有效筛选好的信息，以及避免被不好的信息左右我们的大脑（Critical Thinking）关于以上提到的几点我在豆瓣上有一个专门的豆列（“学会思考”）。</p><blockquote><p>一个学习小Tip：学习一个小领域的时候，时时把“最终能够写出一篇漂亮的Survey”放在大脑中提醒自己，就能有助于在阅读和实践的时候有意无意地整理知识的结构、本质和重点，经过整理之后的知识理解更深刻，更不容易忘记，更容易被提取。</p></blockquote><h2 id="什么才是你的不可替代性和核心竞争力"><a href="#什么才是你的不可替代性和核心竞争力" class="headerlink" title="什么才是你的不可替代性和核心竞争力"></a>什么才是你的不可替代性和核心竞争力</h2><p>我虽不是经济学专业，但是翻开任何一本经济学的教材，或者直接翻开 wikipedia 的 economics 条目，都会看到物以稀为贵这条铁律。人才作为资源的一种，也是同样的道理。而稀缺性，换种说法也可以叫做不可替代性。一种资源越是稀缺，不可替代性就越强。再加上如果这种资源是一种具有实实在在使用价值的东西（而不是荷兰的郁金香泡沫），那么其价格就会越高。</p><p>问题是，如何构筑你的个人知识体系，使得你的知识技能集尽可能成为不可替代的呢？</p><p>CSDN 的孟岩先生前段时间发表了一篇博客“技术路线的选择重要但不具有决定性”，用有说服力的数据阐述了技术路线的选择对于个人知识体系的不可替代性并非一个关键因素，文中也提到了这样一段话：</p><blockquote><p>那么核心竞争力是什么？我观察圈子里很多成功和不成功的技术人，提出一个观点，那就是个人的核心竞争力是是他独特的个性知识经验组合。这个行业里拥挤着上百万聪明人，彼此之间真正的不同在哪里？不在于你学的是什么技术，学得多深，IQ多少，而在于你身上有别人没有的独特的个性、背景、知识和经验的组合。如果这种组合，1，绝无仅有；2，在实践中有价值，3，具有可持续发展性，那你就具备核心竞争力。因此，当设计自己的发展路线时，应当最大限度地加强和发挥自己独特的组合，而不是寻求单项的超越。而构建自己独特组合的方式，主要是通过实践，其次是要有意识地构造。关于这个观点，话题太大，我不打算赘述。</p></blockquote><p>孟岩先生在文中没有对这个问题展开叙述。但我一直也在寻思这个问题，后来在 <a href="https://groups.google.com/g/pongba">TopLanguage</a> 上一次讨论的时候，把一些想法整理成形。</p><p>长话短说，我相信以下的知识技能组合是具有相当程度的不可替代性的：</p><ol><li><p><strong>专业领域技能：</strong> 成为一个专业领域的专家，你的专业技能越强，在这个领域的不可替代性就越高。这个自是不用多说的。</p></li><li><p><strong>跨领域的技能：</strong> 解决问题的能力，创新思维，判断与决策能力，Critical-Thinking，表达沟通能力，Open Mind 等等。</p></li><li><p><strong>学习能力：</strong> 严格来说学习能力也属于跨领域的技能，但由于实在太重要，并且跨任何领域，所以独立出来。如何培养学习能力，到目前为止我所知道的最有效的办法就是持续学习和思考新知识。</p></li><li><p><strong>性格要素：</strong> 严格来说这也属于跨领域技能，理由同上。一些我相信很重要的性格要素包括：专注、持之以恒、自省（意识到自己的问题所在的能力，这是改进自身的大前提）、好奇心、自信、谦卑（自信和谦卑是不悖的，前者是相信别人能够做到的自己也能够做到，后者是不要总认为自己确信正确的就一定是正确的，Keep an open mind）等等。</p></li></ol><ul><li>注：<ul><li>以上将个人的核心竞争力分为4个部分，其中每个部分的罗列并不一定详尽，也有可能我忽略了重要的东西或罗列了不重要的东西，所以欢迎补充和纠正。</li><li>以上只是我个人所认为的具有相当程度不可替代性的知识技能集，至于是否有更具不可替代性的“装备”，不妨思考。</li></ul></li></ul><h2 id="延伸观点"><a href="#延伸观点" class="headerlink" title="延伸观点"></a>延伸观点</h2><ol><li><strong>一件事情如果你没有说清楚，十有八九不能做好。</strong></li></ol><h2 id="扩展阅读"><a href="#扩展阅读" class="headerlink" title="扩展阅读"></a>扩展阅读</h2><ul><li><a href="http://mindhacks.cn/2008/07/08/learning-habits-part1/">刘未鹏：一直以来伴随我的一些学习习惯(一)：学习与思考</a></li><li><a href="http://mindhacks.cn/2008/07/20/learning-habits-part2/">刘未鹏：一直以来伴随我的一些学习习惯(二)：时间管理</a></li><li><a href="http://mindhacks.cn/2008/09/17/learning-habits-part3/">刘未鹏：一直以来伴随我的一些学习习惯(三)：阅读方法</a></li><li><a href="http://mindhacks.cn/2008/12/05/learning-habits-part4/">刘未鹏：一直以来伴随我的一些学习习惯(四)：知识结构</a></li><li><a href="http://mindhacks.cn/2009/01/14/make-yourself-irreplacable/">刘未鹏：《什么才是你的不可替代性和核心竞争力》</a></li><li><a href="http://mindhacks.cn/2008/12/18/how-to-think-straight/">刘未鹏：如何清晰地思考</a></li><li><a href="https://blog.csdn.net/myan/article/details/3247071">孟岩：《技术路线的选择重要但不具有决定性》</a></li></ul><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="/images/post/book/01-darktime.jpg" alt="《暗时间》中的学习习惯与核心竞争力"&gt;&lt;/p&gt;
&lt;p&gt;初看《暗时间》，以为是一本探讨时间聚焦的书籍。细读之后方才发现，暗时间的提法只是全书的起点，而后作者通过亲身经历和自己总</description></item><item><title>无聊科技正经事周刊（第9期）：周刊调整</title><category>科技周刊</category><category>开源</category><pubDate>Tue, 31 May 2022 16:10:11 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/06/01/weekly-9/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-9/cover.jpg"></p><p>Pexels . Norbert Kundrak . (<a href="https://www.pexels.com/zh-cn/photo/3625023/">via</a>)</p><h2 id="本周话题：周刊调整"><a href="#本周话题：周刊调整" class="headerlink" title="本周话题：周刊调整"></a>本周话题：周刊调整</h2><h3 id="1、周刊未来会有哪些调整？"><a href="#1、周刊未来会有哪些调整？" class="headerlink" title="1、周刊未来会有哪些调整？"></a>1、周刊未来会有哪些调整？</h3><p>永久移除：</p><ul><li>资源、工具、文章、图片栏目</li></ul><p>动态保留：</p><ul><li>播客、文摘、言论栏目</li></ul><h3 id="2、为什么调整周刊框架？"><a href="#2、为什么调整周刊框架？" class="headerlink" title="2、为什么调整周刊框架？"></a>2、为什么调整周刊框架？</h3><p>其一，授人以鱼，不如授人以渔</p><p>资源、工具、文章、图片此类栏目，基本来源于我日常订阅的 Newsletter 和 RSS，我仅在每日阅读期间进行筛选搬运，内容相对主观，有失偏颇，所以我决定移除此些栏目，后续把 Newsletter 和 RSS 单独分享给大家。</p><p>其二，精力有限，释放自我</p><p>搬运内容，很耗费精力时间，所以授之以渔之后，很大程度减轻了自己的负担，也能很大程度释放自己的时间，否则每周经常出现被周刊内容绑架的压力。</p><p>其三，两个新想法</p><p><strong>首先想做一个效能平台</strong>。</p><p>一个人，一个组织，没有沉淀是一件很可怕的事情，目前我所在的企业就是如此，产研历史无法追溯、团队没有明确的成长晋升机制/路线、业务产品研发运维缺乏一体化工具协同、没有目标管理、没有组织力，没有标准化，于是乎，大家没有一致认同的奋斗模型，很难协同战斗。</p><p>所以，不如把我闭源的项目做成效能平台？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-9/topic-002.jpg"></p><p><strong>其次想做一个人工智能周刊项目</strong>。</p><p>针对目前自己在周刊上所做之事，抽象起来有点大数据处理分析的味道吗？内容采集、加工清洗、成品输出… </p><p>所以，不如把流水线交由智能自动化来实现，如此一来，我不仅抽离出来，后续要想达到每日一刊，也是极其简单。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-9/topic-001.jpg"></p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/440665194">涛思数据创始人陶建辉：爱捣腾的53岁程序员</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-9/boke-001.jpg"></p><p>陶建辉是涛思数据创始人和CEO，开源了物联网大数据平台TDengine，多次创业与被收购。</p><p>创业前，他曾在美国摩托罗拉和3Com工作，后来回国先后创办了和信和快乐妈咪两家公司（均被成功收购）。陶建辉本科和硕士毕业于中国科技大学，后赴美国印第安纳大学攻读天体物理博士。</p><p>在本期节目中，聊了陶建辉从天体物理到 IT 行业的转变，三次连续创业的经历与反思，对物联网的前瞻性见解，决定开源背后的思考，还包括初创公司如何招聘早期员工、寻找投资人的策略等话题。</p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://mp.weixin.qq.com/s/-XoazO5iZdhga8bZQvre1g">我写的别人都知道怎么办？</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-9/post-001.jpg"></p><p>如果你刚迈入写作行列，或是曾经参与真实写作，是否有过这个困扰：</p><p>“ <strong>我觉得我写的东西，别人都知道！</strong> ”</p><p>文摘围绕这个困扰简述了几个观点，让我深受启发：</p><ul><li>阳光底下没有新鲜事，认为自己总能写出别人不知道的新东西，是一种盲目的傲慢。</li><li>读者并不介意把自己知道的道理再重温一遍，人们不喜欢的是简单重复，没有新意的讲述方式。</li><li>写作者就是通过大量阅读，找到那些生动的故事，然后组合上被验证过的道理，变成内容。</li><li>不要重新去发明一遍轮子，把已经有的素材+自己的想法做一个对接，就能创造出很好的内容来。</li><li>全世界所有的故事模型加在一起也就30来种，最受欢迎的其实不超过三种。</li></ul><p>所以，如果你还在痛苦不知道到底应该写什么，或者像我曾经那样自我怀疑，我写的东西别人都知道了，那不妨就尝试着放松心态，把 <strong>已经有的素材</strong> + <strong>自己的想法</strong> 做一个对接吧。</p><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p>“问问题是我们日常中很重要的一个思维方式。提出问题就已经解决了问题的一半。我们的问题反映出我们是谁，我们将续去向何方以及我们的沟通方式，但很少人知道如何系统有效的提问。”</p><p>– <a href="https://time.geekbang.org/column/article/94077">你真的会问问题吗？</a> （文中引用美国著名记者弗兰克. 赛斯诺）</p><h2 id="订阅"><a href="#订阅" class="headerlink" title="订阅"></a>订阅</h2><p>每周一刊，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li></ul><p>本刊开源，欢迎投稿，仓库在</p><p><a href="https://github.com/senlypan/weekly">https://github.com/senlypan/weekly</a></p>]]></content:encoded><description>&lt;h2 id="封面图"&gt;&lt;a href="#封面图" class="headerlink" title="封面图"&gt;&lt;/a&gt;封面图&lt;/h2&gt;&lt;p&gt;&lt;img src="http://weekly.panshenlian.com/_media/images/2022/issue-9</description></item><item><title>无聊科技正经事周刊（第8期）：让子弹飞一会儿，用长期的眼光看中国开源</title><category>科技周刊</category><category>开源</category><pubDate>Tue, 24 May 2022 17:41:11 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/05/25/weekly-8/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/cover.jpg" alt="封面图"> </p><p>位于墨尔本北菲茨罗伊的一处草屋建筑。（<a href="https://thedesignfiles.net/2022/05/architecture-we-should-be-so-lucky-multiplicity/">via</a>）</p><h2 id="本周话题：让子弹飞一会儿，用长期的眼光看中国开源"><a href="#本周话题：让子弹飞一会儿，用长期的眼光看中国开源" class="headerlink" title="本周话题：让子弹飞一会儿，用长期的眼光看中国开源"></a>本周话题：让子弹飞一会儿，用长期的眼光看中国开源</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/topic-001.jpg" alt="topic"> </p><p><strong>突然闭源，沸沸扬扬</strong></p><p>5月18日，中国开源代码托管平台 Gitee 上的开源仓库（项目），在访客访问时提示仅限仓库成员可见，并声明需要仓库管理员提交公开仓库访问申请，经平台人工审核通过后，才可再次公开访问。简而言之，以前公开的开源代码仓库，现在需要先审后开。</p><p>此举瞬间在网上掀起一阵热议，其中不乏激昂者，觉得此举荒唐！与开源理念相悖！也不乏调侃者，认为这是在自己掐自己，雪上加霜！更不乏玻璃心，断定中国开源已死！</p><p>显然，大家在宣泄情绪，这是人类对抗新变化/维持现状的一种体内平衡方式，带有感性标签。本质上许多人都能看懂，此举并没有多大玄机，但同时又有许多人看不明白，至于吗？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/topic-002.jpg" alt="topic"> </p><p><strong>开源越界，没有法外之地</strong></p><p>在好奇心驱使下，我们像极了一个翻石头的孩子，开始猜测 Gitee 行为背后的可能性：</p><ul><li>有人提交了不恰当言论，甚至造谣..</li><li>有人破坏了公平规则，公开抢菜、抢票、抢苗代码..</li><li>有人公开了某些特殊词库、某些非法影音资料..</li><li>有人发布了危害未成年的相关代码，造成不良影响..</li><li>有人把企业私有代码公开于众..</li><li>More…</li></ul><p>总之，大概率是有人越了界，侵了权，违了规，过去平台野蛮放任的作风，如今需要适度往回收一收，需要卡个小口，把这群害码筛查过滤一遍，让恶人无处作恶，甚者追究责任。</p><p>内容平台就必然存在违规风险，所以审核是一种必不可少的监管方式。短期一看，平台和用户都有点小难受，但从长远的角度分析，既保障了开源软件事业长远稳健可控，又夯实了开源平台持续良性向好。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/topic-003.jpg" alt="topic"> </p><p><strong>开源危机，卡脖子的事</strong></p><p>此前俄罗斯在 Github 上被卡脖子的事实，让国人更加坚信：开源虽无边界，但平台有国界。其中，无论是大面上的开源还是具体至于芯片，国际多方的激烈竞争都加速了这一事实，所以国家层面的大力推进支持自然不必说，在这样的背景下，Gitee 纵使有千万种理由可以吐槽，也始终作为我们国人开源事业的砥砺者，现阶段毫无疑问似个婴儿，学步之初难免磕碰跌倒、甚至做出愚蠢之事，因此更需要外部的支持力量，譬如你我，去额外呵护、耐心栽培，促使他茁壮成长、补质补短、寻求突破，甚至超越，社区起来了，人才进来了，质量拔高了，依赖消减了，我们才有可能翻盘，才有赢面。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/topic-004.jpg" alt="topic"> </p><p><strong>别急，让子弹飞一会儿</strong></p><p>从18日至今，不到一周时间，今早（25日）我看 Gitee 上的项目基本已正常可见。</p><p>Gitee 做出的无奈最优解，自始至终冷静从容，反过来作为开源社区的一份力量，你我显然需要更加成熟，同样秉持这份心态，让子弹飞一会儿，莫着急，给点过程，不能说立刻见效，这也是一种心态，懂了，我们这一代代人，才有谱。</p><p>毫无疑问，Gitee 此次审核事件，很可能会成为一段争议的历史，在后人口中传开议论，而置于事件旋涡中心的你我，到底是义愤填膺、冷嘲热讽、还是心平气和…</p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://appleinsider.com/articles/22/05/23/iphone-14-to-get-high-end-front-camera-with-built-in-autofocus">iPhone 14 将配备内置自动对焦的“高端”前置摄像头</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-001.jpg" alt="iPhone"> </p><p>（据称）即将面世的iPhone 14和 iPhone 14 Pro 可能配备内置自动对焦功能的高端自拍相机，该相机配置将交由韩国 LG Innotek 组装。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-002.jpg" alt="iPhone"> </p><p>据ET News 报道，这一变化是由于苹果将前置摄像头归类为 “高端” 组件而不是 “低端” 组件，因此供应商从中国换为韩国。</p><p>iPhone 14 升级前置摄像头，支持自动对焦功能，以增强 FaceTime 通话和自拍功能，而且可能采用更大的光圈，可以为 iPhone 机型提供更好的人脸识别功能。</p><p>2、<a href="https://www.odditycentral.com/news/people-are-getting-cosmetic-voice-surgery-to-change-the-way-they-sound.html">人们正在接受美容语音手术以改变他们的发声方式</a>（英文）</p><p>何曾几时你希望自己拥有一副深沉嗓音，如今这是一个可实现的目标，土耳其的外科医生 Kursat Yelken 可以提供帮助。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-006.jpg" alt="美容语音手术"> </p><p>(<a href="https://pixabay.com/ro/photos/%c5%a3ip%c4%83t-strig%c4%83t-femeie-fric%c4%83-furie-4751647/">via</a>)</p><p>Kursat Yelken 博士从事声音加深手术 15 年，并称该手术的需求一直在增加。目前，他每年为 100 至 150 人服务，从希望听起来更自信的高管到希望让自己的声音与新性别相匹配的跨性别者。</p><p>这位在哈佛医学院接受过培训的土耳其医生可以根据患者的喜好调整患者的音调和语调，使他们的声音更深或更高。</p><p>Yelken 博士用外行的术语解释了这个过程，他说它首先在颈部下部切开一个切口，然后在甲状腺软骨上创建一个“软骨岛”，该软骨岛也被推回以发出声音更深。该操作是在局部麻醉期间进行的，因此患者可以通过尝试他们的新声音来选择希望的音调和音色。</p><p>3、<a href="https://appleinsider.com/articles/22/05/21/apple-store-opens-in-wuhan-with-chinas-first-apple-pickup-area">武汉 Apple Store 开业，开设中国首个 Apple Pickup 到店取货区域的零售店</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-003.jpg" alt="Store"> </p><p>Apple 武汉零售店于 5 月 21 日 (周六) 在湖北正式开幕，位于武汉国际广场购物中心 C 座 2F 。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-004.jpg" alt="Store"> </p><p>这是中国首个设立专属 Apple Pickup 到店取货区域的零售店。至此，苹果大中华区 Apple Store 零售店总数达到 54 家。店内拥有 128 人组成的零售店团队，可以通过多种语言与顾客交流，包括中文手语和英文手语。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-005.jpg" alt="Store"> </p><p>该商店是中国第一家设有专门的 Apple Pickup 区域的商店，因此客户可以快速轻松地获得在线订单。Today at Apple 课程也将在场地提供，涵盖的主题包括摄影、摄像、艺术与设计、音乐和编程。</p><p>4、<a href="https://interestingengineering.com/moon-volcanoes-volcanic-water">月球火山可能喷出 18 万亿磅的火山水</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-007.jpg" alt="月球火山"> </p><p>月球过去猛烈的火山活动可能为宇航员在未来的任务中提供重要的生命续航。</p><p>月球上那些特征性的黑色斑点，也称为月海，起源于数十亿年前，当时月球表面发生了一系列火山爆发。</p><p>现在，加拿大大学博尔德分校（CUBoulder）研究人员的一篇新论文预测，这些火山可能还留下了冰盖，部分冰盖的厚度可能高达数百英尺，这些冰盖可以用于饮用水和火箭推进剂。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/keji-008.jpg" alt="月球火山"> </p><p>美国宇航局的阿尔特弥斯计划旨在建立人类在月球上的存在，这可以作为未来火星任务的垫脚石，自 1972 年以来的首次载人登月预计将在 2025 年左右进行。</p><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://elements.visualcapitalist.com/ranked-top-10-ev-battery-makers/">排名：前 10 名电动汽车电池制造商</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-001.jpg" alt="电动汽车电池制造商"> </p><p>▲ 统计数据早于 2021年 10 月</p><p>随着消费者对电动汽车 (EV) 的兴趣日益浓厚，锂离子 EV 电池市场现在是每年约 270 亿美元的业务。</p><p>业内专家表示，高需求提振了电池制造商的利润，给市场带来了激烈的竞争。到 2027 年，随着消费者接受更实惠的电动汽车，市场可能进一步增长到 1270 亿美元。</p><p>作为汽车零部件的制造强国之外，亚洲正迅速成为电池领域创新的温床。</p><p>市场份额排名前 10 的电动汽车电池制造商都将总部设在亚洲国家，集中在<strong>中国、日本和韩国</strong>。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-002.jpg" alt="电动汽车电池制造商"></p><p>▲ 前三大电池制造商——CATL（<strong>宁德时代</strong>）、LG 和松下——占据了电动汽车电池制造市场近 70% 的份额。</p><p><strong>电动汽车的巅峰可能已经过去，但是全面电动化趋势却是必然</strong>，基本有几点原因：</p><ul><li>推广政策（环境与清洁能源）</li><li>消费意识（认知与诉求）</li><li>多元型号（制造商与受众）</li><li>电池价格（创新与竞争优势）</li><li>more</li></ul><p>当然，电动化趋势的阻碍原因也同时存在：</p><ul><li>电池金属供应（大环境与局部紧张）</li><li>充电基础设备（普及与便利）</li><li>充电时间（技术突破）</li><li>续航里程（焦虑与改进）</li><li>more</li></ul><p>2、<a href="https://ciechanow.ski/mechanical-watch/">可视化图解机械表工作原理</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-003.jpg" alt="机械表"></p><p>在现代便携式设备的世界中，很难相信仅仅在几十年前，最方便的计时方式是<strong>机械表</strong>。</p><p>与石英表和智能表不同，机械表<strong>无需</strong>使用任何电池或其他电子元件即可运行。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-004.jpg" alt="机械表"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-005.jpg" alt="机械表"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-006.jpg" alt="机械表"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-007.jpg" alt="机械表"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-008.jpg" alt="机械表"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-009.jpg" alt="机械表"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-010.jpg" alt="机械表"></p><p>文章使用可交互的 <strong>3D 动画</strong>效果演示了机械表工作原理、零部件组装过程等。</p><p>特别是你可以拖动动画来改变你的<strong>视角</strong>，并且使用滑块来查看内部运行机制，动画设计得无比到位。</p><p>3、<a href="https://codepen.io/kaz_hashimoto/pen/KKQdMrY">CSS 太阳光线穿过窗户</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-011.jpg" alt="CSS"></p><p>工程师 Kazuhiro Hashimoto 编写了一个令人惊叹的、可定制的动画场景——太阳光线穿过窗户。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/linggan-012.jpg" alt="CSS"></p><p>当然，如果你喜欢这套效果，可直接复制 HTML、CSS、JS ，可能对你的设计有些许启发。</p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://mp.weixin.qq.com/s/Oo9JNMTMNzaIWizOdYXGkw">35岁从大厂裸辞，我该何去何从</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-001.jpg" alt="大厂裸辞"></p><p>最近从陈成老师那看到一位阿里的12年老兵裸辞，可以简单看看、听听，可能不适合大部分人。</p><p>2、<a href="https://raft.github.io/">Raft 共识算法介绍（极致内容）</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-004.jpg" alt="Raft"></p><p>相信设计分布式系统的工程师们都了解共识的核心作用，网络间通信的服务只要涉及多节点，并希望对外提供统一的能力，那各节点间必然需要达成一种共识，而达成共识就需要有一种普遍任何的共识机制，所以共识算法，支撑这一切。</p><p>不仅仅是分布式系统，区块链等领域的实现，都基于共识。</p><p>▼ 其中，你可能了解过 NWR、Gossip、Paxos、Zab 等协议/算法，但在这里如果你详细了解以下关于 Raft 的介绍、可视化过程以及相关论文之后，无论是原始的 Gossip 还是其它共识变种，你都会融通，基本大差不差，不过是边界差异。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-002.jpg" alt="Raft"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-005.jpg" alt="Raft"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-003.jpg" alt="Raft"></p><p>▲ Raft 算法可视化动画设计得简单明了，建议你尝试体验，有助于了解主观下线、选举等共识达成过程。</p><p>3、<a href="https://leovan.me/cn/2022/05/rating-and-ranking-algorithms/">评分和排名算法 (Rating &amp; Ranking Algorithms)</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-006.jpg" alt="评分和排名算法"></p><p>投票是对不同选项或个体的排序，在投票中我们关注更多是<strong>相对位置</strong>这样定性的结论，例如：积分前三名的同学才能进入下一环节。但有的时候我们不光想知道不同选项之间的先后顺序，还想了解不同选项之间的<strong>差异大小</strong>，这时我们就需要<strong>设计更精细的方法</strong>进行定量分析。</p><p>范叶亮划分出四种具体的评分排名，并分别罗列典型网站案例：</p><ul><li>基础评分和排名</li><li>考虑时间的评分和排名</li><li>不考虑时间的评分和排名</li><li>比赛评分和排名</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-007.jpg" alt="评分和排名算法"></p><p>▲ 其中，个人对于考虑时间的评分和排名类目中的 <strong>Hacker News</strong> 计算公式极为好奇。</p><p>Hacker News 是一个可以发布帖子的网络社区，每个帖子前面有一个向上的三角形，如果用户觉得这个内容好，点击一下即可投票。根据得票数，系统自动统计出热门文章排行榜。</p><p>Hacker News 使用分数计算公式如下：</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-008.jpg" alt="评分和排名算法"></p><p>其中，<strong>P</strong> 表示帖子的得票数，减去 <strong>1</strong> 表示忽略发帖人的投票；<strong>T</strong> 表示当前距离发帖的时间（单位为小时），加上 <strong>2</strong> 是为了防止最新的帖子分母过小；<strong>G</strong> 为重力因子，即将帖子排名被往下拉的力量，默认值为 <strong>1.8</strong>。</p><p>在其他条件不变的情况下，更多的票数可以获得更高的分数，如果不希望“高票数”帖子和“低票数”帖子之间差距过大，可以在式 <strong>(1)</strong> 的分子中添加小于 <strong>1</strong> 的指数，例如： (P-1)^0.8 。在其他条件不变的情况下，随着时间不断流逝，帖子的分数会不断降低，经过 <strong>24 小时</strong> 后，几乎所有帖子的分数都将小于 <strong>1</strong>。重力因子对于分数的影响如下图所示：</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-009.jpg" alt="评分和排名算法"></p><p>不难看出， <strong>G</strong> 值越大，曲线越陡峭，排名下降的速度越快，意味着排行榜的更新速度越快。</p><p>4、<a href="http://ifeve.com/%E5%85%B3%E4%BA%8E%E8%AF%81%E4%B9%A6%E8%BF%99%E9%87%8C%E6%9C%89%E4%BD%A0%E6%83%B3%E7%9F%A5%E9%81%93%E7%9A%84%E4%B8%80%E5%88%87-md/">关于证书,这里有你想知道的一切</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-010.jpg" alt="关于证书"></p><p>在HTTPS开发中，你是否被各种证书搞得晕头转向？</p><p>.pem证书?<br>.der证书?<br>X.509证书？</p><p>本文带你理清这些证书叫法背后的含义。</p><p>5、<a href="https://hackernoon.com/how-to-make-a-great-looking-website-even-if-you-cant-design">如何搭建一个漂亮的网站，即使你不会设计</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-012.jpg" alt="网站"></p><p>本指南介绍了用于设计网站的工具、技术和资源，100% 免费，帮你快速搭建一个有趣/有创意的网站。</p><p>6、<a href="https://news.ycombinator.com/item?id=31438426">一个相当优秀的开发者的孤独感</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/article-013.jpg" alt="10x"></p><p>一个 “相当优秀” 的10x 开发者的孤独自述，33 岁才入行，目前 42 岁。</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://uiverse.io/">uiverse.io：精美的免费开源 UI 组件库</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-001.jpg" alt="uiverse"></p><p>uiverse.io，一款精美的 UI 组件库，免费开源</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-002.jpg" alt="uiverse"></p><p>▲ 平台有很多顶级设计师，你也可以设计组件</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-003.jpg" alt="uiverse"></p><p>▲ 看到喜欢合适的组件，可直接复制使用 HTML 和 CSS 代码</p><p>2、<a href="https://www.shrink.media/upload">Shrink.media：极致压缩图像大小的在线工具</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-004.jpg" alt="Shrink"></p><p>Shrink.media，一款免费压缩图像大小的在线工具，通过智能压缩和缩小尺寸来减小图像的文件大小，并支持免费下载压缩后的图像</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-005.jpg" alt="Shrink"></p><p>▲ 压缩前后对比，从360kb至12.35kb，效果满意</p><p>3、<a href="https://www.typelit.io/">TypeLit.io：一个练习打字的网站</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-006.jpg" alt="TypeLit"></p><p>通过练习你最喜欢的文学作品来提高你的打字速度。支持：英语/荷兰语/芬兰/法语/德语/意大利语/葡萄牙语/俄语/西班牙语。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-007.jpg" alt="TypeLit"></p><p>除了网站提供的经典文学作品，目前发布了最新功能，支持<strong>自带书籍</strong>，其实就是允许你导入未加密的 EPUB、PDF 和 TXT 文件，然后开始你的打字练习。</p><p>当然，这个功能收费，5 刀/月，如果你只是练习打字（字母/单词），那其实免费版本足够使用了。</p><p>4、<a href="https://github.com/rzashakeri/beautify-github-profile">beautify-github-profile：美化你的 GitHub 个人资料</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-008.jpg" alt="beautify"></p><p>beautify-github-profile 仓库提供了一整套工具和指南，帮助你设计出一个更漂亮、更有吸引力的 github 个人资料展示页。</p><p>5、<a href="https://apitracker.io/">API Tracker：一个API的追踪器</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/tools-009.jpg" alt="apitracker"></p><p>apitracker.io ，可以发现最好的 API 和开发者资源，包括跟踪 API 规范、开发人员体验、API 文档、SDK、开发人员文档、IDE 支持、平台强度、身份验证和 API 样式。</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://myfirstnft.info/">我的第一个NFT</a>（中文/英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-005.jpg" alt="NFT"></p><p>如果你想：理解 NFT 的价值、了解 web3、制作一个免费的 NFT..建议你先Mark为敬。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-006.jpg" alt="NFT"></p><p>教程体验极致，舒适度五星。</p><p>2、<a href="https://www.bookstack.cn/">书栈网</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-001.jpg" alt="书栈网"></p><p>书栈网，汇集了众多 IT 程序员互联网开源编程书籍，囊括小程序、前端、后端、移动端、云计算、大数据、区块链、机器学习、人工智能和面试笔试等相关书籍。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-002.jpg" alt="书栈网"></p><p>特别你所希望看到的面经算法、编程电子书。</p><p>3、<a href="https://global.canon/zh/c-museum/camera-series.html">佳能相机博物馆</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-003.jpg" alt="佳能"></p><p>佳能相机博物馆，从 30 年代的胶片相机到目前的数码相机，一应俱全。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-004.jpg" alt="佳能"></p><p>支持搜索，排序，相机种类极多。</p><p>4、<a href="https://sideproject.guide/">Side Project 指南</a>（中文）</p><p>工作之余，很多程序员喜欢用代码建造一些小工具。有时候是为了解决一些自己或者朋友遇到的小问题，有时候是为了好玩儿。我们这行，通常称之为 Side Project。 </p><p>不过可不能小看这些小工具。许多伟大的产品诞生于程序员的 Side Project。比如 GitHub, Unsplash, Instagram, 等等等等。</p><p>每次开启一个项目，虽然是 Side Project, 但内心其实隐隐也在期待着，这个 Side Project 或许可以帮到除自己以外的人？或许可能，这个产品价值足够高，甚至带来经济回报？然而现实是，99% 的 Side Project 都永远停留在自娱自乐的情况。</p><p>经历三个阶段：从 热情满满 到 无人问津 最后 弃坑。</p><p>Side Project 指南详细介绍了如何提升 Side Project 的存活概率、每次开启一个新项目都应该问自己下面四个问题：</p><ul><li>如何判断一个点子的好坏？</li><li>如何快速把 Side Project 做出来？</li><li>如何让更多人知道这个 Side Project？</li><li>如何把 Side Project 变成能为自己带来收入的产品？</li></ul><p>5、<a href="https://landscape.cncf.io/">CNCF（云原生计算基金会）全景一览</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-007.jpg" alt="CNCF"></p><p>▲ CNCF 全景一览</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-008.jpg" alt="CNCF"></p><p>▲ CNCF 成员</p><p>本资源不做介绍，懂的都懂，不懂的不懂，仅列为自己收藏的一副星辰大海图。</p><p>6、<a href="https://awesomeindie.com/">Awesome Indie，一个发现独立开发者产品的网站</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-009.jpg" alt="Awesome"></p><p>一个发现独立开发者产品的网站，上周上榜 ProductHunt #1 的一个产品。</p><p>7、<a href="https://www.devdum.com/">DevDum - 200多个面向 Web 开发人员的资源导航网站</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-010.jpg" alt="Awesome"></p><p>像素风格，挺特别。</p><p>8、<a href="https://hackertalk.net/">黑客说</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/source-011.jpg" alt="黑客说"></p><p>国产技术交流社区，有点 Twitter 风格，试运营中，暂时没看到有吸引我的点，如果想达到优质技术交流水平，很难很难。</p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/531293360">什么是 DAO？与创始人和资深参与者聊聊 DAO</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/boke-001.jpg" alt="DAO"></p><p>有人说 <strong>DAO</strong> 是 <strong>公司制度的未来</strong>，也有人对此不屑一顾。可究竟什么是 DAO，仍然众说纷纭。</p><p>本期节目我们请到了 DAOrayaki 的创始人 Wendy 与 Bankless、fwb、Seed Club 等 DAO 的资深参与者王超来一起来聊聊 DAO 背后的故事。</p><p>本期节目我们聊到了：</p><ul><li><ol><li>DAORayiki 的来龙去脉；王超为什么加入这些 DAO</li></ol></li><li><ol start="2"><li>为什么选择 DAO 的形式、什么是 DAO？DAO 究竟是怎么运作的？</li></ol></li><li><ol start="3"><li>汉洋是做开源的，我们之前的开源项目和 DAO 有哪些区别呢？它是一个老的事情套上了新的名词，还是一件全新的事情？</li></ol></li><li><ol start="4"><li>如何加入一个 DAO？</li></ol></li><li><ol start="5"><li>DAO 怎么挣钱？</li></ol></li></ul><p>2、<a href="http://tv.cctv.com/2022/04/25/VIDAtlLjsM5mDyjKGNMcNTPZ220425.shtml">CCTV记录片《码农的异想世界》</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/boke-002.jpg" alt="码农的异想世界"></p><p>在外人眼中，程序员们（自嘲码农）仿佛永远穿着格子衫、大裤衩，捋着堪忧的发量、不善与人言辞。本片通过采访不同岗位的码农，听他们讲述自己与字符相作、乏善可陈的生活和闪闪发光的科学梦想。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/boke-003.jpg" alt="码农的异想世界"></p><p><strong>目前已播出剧集</strong>：</p><ul><li>《码农的异想世界》 第1集</li></ul><p>本集主要内容： 这些行色匆匆的脚步，奔忙在中国互联网行业最密集的地方——西二旗，脚步的主人们有一个共同的代号——码农，他们肩上并未扛着锄头，头脑却耕耘在数码的田野里。每天的高峰时刻，6万多码农，进出于这个北京最繁忙的地铁站。</p><ul><li>《码农的异想世界》 第2集</li></ul><p>本集主要内容： 肖玮，一名音频算法工程师，也就是我们俗称的“码农”、“程序员”。他正在研发的AI降噪算法，如果技术可行，将为听力衰减的老人提供辅听，从而提升声音的清晰度和可懂度。</p><ul><li>《码农的异想世界》 第3集</li></ul><p>本集主要内容： 每一位码农都有两个身份：现实世界的程序员、异想世界中的造物者。程序员专注于编写代码，而造物者负责构建起一个虚幻的世界，这个虚幻的世界可以很小，可以很大，也可以无穷大。他们在现实与异想世界间穿梭，凭借一行行代码，实现内心最深处的愿望。</p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://tinyprojects.dev/posts/i_spent_two_years_launching_tiny_projects">我花了 2 年时间启动的小型项目</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/post-001.jpg" alt="i_spent_two_years_launching_tiny_projects"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/post-002.jpg" alt="i_spent_two_years_launching_tiny_projects"></p><p>作者介绍自己两年间落地八个想法，其中部分项目持续盈利，特别是分享了自己在构建这些小型互联网项目方面所学到的知识以及成果。</p><p>这是他的小项目，例如：</p><ul><li>个人网站 tinyprojects.dev</li><li>收集购入大厂域名，例如 netflix.soy</li><li>Android 版大逃杀游戏</li><li> 小型电子商务商店 oneitem.store</li><li>一个非常正常的社交网络平台 Snormal</li><li>稀有用户名 Earlyname</li><li>emoji 域名电子邮件服务</li><li>纸质网站 Paper Website</li></ul><p>特别羡慕这种生活工作方式，这也是为何我希望成为ZY职业者的初心。</p><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://twitter.com/wenquai/status/1527312285152452608">DALL-E 可以让奇幻小说栩栩如生</a>（英文）</p><p>OpenAI 前不久公开的 DALL-E，可以为每个段落都动态生成一组独特生动的图像。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/photo-001.jpg" alt="DALL-E"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/photo-002.jpg" alt="DALL-E"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/photo-003.jpg" alt="DALL-E"></p><p>2、<a href="https://www.odditycentral.com/travel/car-sickness-hell-a-winding-mountain-road-with-600-hairpin-turns.html">帕米尔高原天空公路</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-8/cover1.jpg" alt="帕米尔高原天空公路"> </p><p>帕米尔高原天空公路最高点海拔4269m。它最初长 36 公里（22 英里），但后来延长到 75 公里。</p><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p>中国发展太快了，随着全球格局的变化，未来面临的发展机会和挑战都会越来越多。对于我们这一代技术人来说，只要顺应时代的变化，未来的机会只会更多。</p><p>对于新一代的开发者来说，我认为未来的领域会更加集中在用技术和算法进一步改造各种传统的行业，例如：汽车、物联网，以及用技术将人类社会朝着数字化的虚拟世界发展。所以，我推荐大家关注自动驾驶、人工智能、产业互联网、推荐系统、虚拟现实等领域，可能会有新的行业红利出现。</p><p>– <a href="https://blog.devtang.com/2022/05/22/startup-10th-year-summary/">《我的移动开发程序人生 - 写在创业十周年》</a></p><p>2、</p><p>面向未来不一定是软件开发的目标，而是影响设计的众多 “能力” 之一。</p><p>在这个范围内过度纠正可能会导致不必要的工作、技术债务和令人困惑的抽象。</p><p>不过，找到最佳点可以节省你的时间和精力。</p><p>– <a href="https://stackoverflow.blog/2022/05/19/crystal-balls-and-clairvoyance-future-proofing-in-a-world-of-inevitable-change/">《水晶球和千里眼：在万变的世界中面向未来》</a></p><p>3、</p><p>如果某种语言的某个特性有时会出现问题，并且可以用另一个更可靠的特性来替换它，那么请始终使用更可靠的特性。</p><p>– <a href="https://baike.baidu.com/item/Douglas%20Crockford/5960317">Douglas Crockford（道格拉斯·克罗克福德）</a></p><h2 id="首发订阅"><a href="#首发订阅" class="headerlink" title="首发订阅"></a>首发订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li></ul><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description></item><item><title>无聊科技正经事周刊（第7期）：跟村上春树学跑步，向古川武士学养成</title><category>科技周刊</category><category>跑步</category><pubDate>Tue, 17 May 2022 18:27:11 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/05/18/weekly-7/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/cover.jpg" alt="村上春树"></p><p>村上春树的黑魔法：写作建议与天赋、专注、耐力。（<a href="https://lithub.com/whats-needed-is-magic-writing-advice-from-haruki-murakami/">via</a>）</p><h2 id="本周话题：向村上春树学跑步，向古川武士学养成"><a href="#本周话题：向村上春树学跑步，向古川武士学养成" class="headerlink" title="本周话题：向村上春树学跑步，向古川武士学养成"></a>本周话题：向村上春树学跑步，向古川武士学养成</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/topic-001.jpg" alt="topic"></p><p><strong>1、近来</strong></p><p>一面坚持晨跑，每日5公里，基本已坚持一个月的时间；一面杂读，改掉了以往只钟爱技术类书籍的毛病。</p><p>自从年纪过了三十之后，脑海中就经常浮现许多敏感之词，像中年危机、健康、财务自由、人生的意义…断断续续、让人不时痛苦又增添烦恼。</p><p>不知何时开始，我索性都放下不去联想，开始花时间在一些相对有意义的事上面，就是开头提到的跑步和阅读。两者都能让人放空，也能够随时随地的开始。</p><p>分享近来读完的两本书籍，分别是村上春树的《当我谈跑步时，我谈些什么》与古川武士的《坚持，一种可以养成的习惯》，恰巧都是出自日本作家之手，都在谈论坚持的意义，不禁感叹人家的思维方式，不光做事，还探究里层的学问，实在让人钦佩。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/topic-002.jpg" alt="topic"></p><p><strong>2、当我谈跑步时，我谈些什么</strong></p><p>村上春树在三十三岁之前是一家爵士酒吧的小老板，之后他试图去写小说，并且参加了日本的一个小说奖，凭借小说处女作《且听风吟》获得第23届群像新人文学奖，之后发表长篇小说《1973年的弹子球》，再然后是鸿篇巨制的《寻羊冒险记》，紧跟着《挪威的森林》。在写作前两年的生活转变过程中，他发现自己的身体逐渐开始变型，每天的桌间生活，使得腰腹间赘肉增长，体质虚弱。那个时候，他一直在寻找一种方式来解决面对的这个问题，后来选择了<strong>跑步</strong>。</p><p>村上春树是三十三岁才开始跑步，他认为还足够年轻，但不能说是“青年”了。这是耶稣死去的年龄，而斯科特·菲茨杰拉德的凋零从这个年纪就开始了。这也许是人生的一个分水岭。</p><p>”Pain is inevitable. Suffering is optional.（痛楚难以避免，而磨难可以选择）”，村上春树在长跑时脑海里反复出现这句话。我大致理解这句话的含义，人生有很多痛楚就像生老病死一样无法避免，但在你一生的朝圣之路上，可以自由选择，很累人快要奔溃之时你大可选择放弃，或是转过头咬牙坚持、选择经历磨难。</p><p>村上春树从起初的乡间小跑，到后来每年参加马拉松，再到后来的铁人三项（游泳、自行车、跑步），他在一步步突破自己的身体极限，完成他心目中一个个目标，而这个过程，他始终是一名作家。</p><p>村上春树也正是基于此，通过实实在在地运动自己的躯体，感悟出一系列类似经验法则的东西。</p><p>他认为写小说其实是一个力气活。但是在跑步的时候，遇到的最大的问题就是如何坚持下去。你经常会早晨醒来，发现自己脚步沉重，身体告诉自己不要去做这件事情，但是只要你不断坚持下去，你的身体才会习惯这样的运动，甚至给予这样的运动以喜悦的反应。肌肉是有记忆的，它会知道你对身体的需求，你偷懒的时候，它更会偷懒，你坚持的时候，突破体能极限的时候，它同样能够坚持和突破，这就是跑步所遇到的问题。</p><p>至此其中，我觉摘抄书中<strong>三处</strong>供互联网技术人思考：</p><ul><li>打算作为小说家度过今后漫长的人生，就必须找到一个既能维持体力，又可将体重保持得恰到好处的方法。</li><li>老实说，我甚至觉得每天坚持跑步同意志的强弱，并没有太大的关联，我能够坚持跑步三十年，恐怕还是因为跑步合乎我的性情，至少“不觉得那么痛苦”。</li><li>持之以恒，不乱节奏，对于长期作业实在至为重要。一旦节奏得以设定，其余的问题便可以迎刃而解。然后要让惯性的轮子以一定的速度准确无误地旋转起来，对待持之以恒，何等小心翼翼亦不为过。</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/topic-003.jpg" alt="topic"></p><p><strong>3、坚持，一种可以养成的习惯</strong></p><p>你的所有行动几乎全部，或至少有95%，是由你本人的<strong>习惯</strong>决定的，而所谓习惯，就是对于外来的刺激做出无意识的反应，或是条件反射式的反应，习惯不依赖意志或毅力，把自己想要持续的事情引导到如每天刷牙般轻松的状态 。无论如何，当身体学会某种行动，不用思考或努力就可以轻松做出反应。一旦某种行动化为习惯，就可以在无意识中进行控制，一旦化为习惯，就可以通过较少的劳功获得较大的成果。</p><p>古川武士分析不能坚持做事的原因，无论是想减肥、想攒钱、想戒烟、还是想培养好习惯，三分热度或半途而废是有其各中道理，特别是想将好习惯坚持下去，需要的不是意志力，而是<strong>正确的方法</strong>。</p><ul><li>首先，认识不同程度的习惯分类，不同程度的习惯需要持续养成的时间自然不同，从行为习惯（阅读/节约/写日记），到身体习惯（减肥/运动/戒烟）、再到思考习惯（逻辑思考/创意能力/正面思考），持续的时间和强度会不断增加，书本量化为一个月、三个月、六个月。</li><li>其次，培养行为习惯需要脱离习惯引力，如同火箭发射脱离地心引力一般，特别是 “火箭升空的最初几分钟、几公里内所耗费的能量远多于后来几天、几十万公里旅程中所耗费的能量”，而培养习惯的过程也类似发射火箭的过程，顺利经历极具挑战的三个阶段（反抗期、不稳定期、倦怠期），脱离引力，此后仅需很少的坚持惯性就能维持很好的效果。</li><li>最后，便是每个阶段的具体对策打法。在反抗期先以婴儿学步开始，不设置过高目标，保证每日达成；不稳定期开始建立持续行动机制，行为模式化，设定例外的规则，并配合十二个“持续开关”诀窍（奖励/称赞/游戏/理想模式/仪式/去除障碍/损益计算/结交朋友/对大众宣布/处罚/设定目标/强制力）激励鞭策自我；倦怠期则是脱离习惯引力的最后反抗，可以通过添加习惯之外的变化，并计划下一项习惯来再次唤活激情。</li></ul><p>书中最后一章节介绍了六个成功故事，包括整理、学英语、节约、减肥、早起、戒烟，其中经历过程以及三个阶段的正确对策几乎详尽细致。</p><p>古川武士认为，只有培养了良好习惯，才能提高工作效率、丰富人生。看起来似乎绕了远路，其实这才是最对的捷径。</p><p>以“农民”的眼光，撒下习惯的种子，此刻便开始！</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/topic-004.jpg" alt="topic"></p><p><strong>4、写在最后</strong></p><p>两本书的字里行间，都在表现长期主义的重要性，无论是跑步、创作或是某项习惯的养成，都需循序渐进，找到科学合理的方法规划自己的行动，配合自己的节奏，不需要非凡的意志力和超人的耐心，坚持一段时间之后，让新行为习惯融入你的日常生活，再坚持一段时间过后，你会感受到一种焕然一新。</p><p>Don’t quit , Do it.</p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://www.techradar.com/news/apple-officially-retires-the-ipod">苹果正式下架 iPod</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/keji-004.jpg" alt="iPod"></p><p>5月10日，苹果宣布 iPod touch（第 7 代）售完即止，不再发售。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/keji-006.jpg" alt="iPod"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/keji-005.jpg" alt="iPod"></p><p>▲  目前苹果市场应该是购买不到了， iPod touch 将活在回忆里。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-001.jpg" alt="iPod"></p><p>▲  最初的 iPod 于 2001 年 10 月 23 日推出，是第一款将惊人的 1,000 首歌曲和 10 小时电池装入令人惊叹的 6.5 盎司包装中的 MP3 播放器。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-002.jpg" alt="iPod"></p><p>▲ iPod mini 于 2004 年 2 月 20 日推出，将用户对 iPod 的所有喜爱融入到仅 3.6 盎司的更小设计中。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-003.jpg" alt="iPod"></p><p>▲ iPod nano（第 2 代）于 2006 年 9 月 25 日推出，提供纤薄的设计、明亮的彩色显示屏、六种时尚的颜色和长达 24 小时的电池续航时间，用户口袋里最多可放 2,000 首歌曲。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-004.jpg" alt="iPod"></p><p>▲ iPod touch 于 2007 年 9 月 5 日首次推出，它带来了革命性的 Multi-Touch 界面，使 iPhone 凭借华丽的 3.5 英寸宽屏显示屏成为 iPod 的热门产品。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-005.jpg" alt="iPod"></p><p>▲ iPod nano（第 7 代）于 2012 年 9 月 12 日推出，是迄今为止最薄的 iPod，仅 5.4 毫米，配备 2.5 英寸多点触控显示屏。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-006.jpg" alt="iPod"></p><p>▲ iPod shuffle（第 4 代）于 2015 年 7 月 15 日推出，采用时尚设计，电池续航时间长达 15 小时，2GB 存储空间可容纳数百首歌曲，还有一个 VoiceOver 按钮可以听到歌曲标题、播放列表名称或电池状态。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/ipod-007.jpg" alt="iPod"></p><p>▲ iPod touch（第 7 代）于 2019 年 5 月 28 日推出，配备 A10 Fusion 芯片，可实现身临其境的增强现实体验和 Group FaceTime，以及 256GB 的存储空间。</p><p>2、<a href="https://edition.cnn.com/2022/05/13/cars/uber-robot-delivery-la/index.html">优步测试机器人送餐</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/keji-001.jpg" alt="优步"></p><p>▲  Nuro</p><p>优步本月将启动两项测试计划，以在大洛杉矶地区提供 UberEats。它将结合使用四轮机器人和自动驾驶汽车，具体取决于交付周期。客户将在优步应用程序中收到有关如何取回食物的说明。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/keji-002.jpg" alt="优步"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/keji-003.jpg" alt="优步"> </p><p>一名安全司机将在场进行汽车交付，并对机器人进行远程监控。从长远来看，自动化交付可以使交付更加实惠，并为更多类型的服务创造市场。</p><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://mp.weixin.qq.com/s/SlxB7j4Mjyj-jzGouwHkqg">5G，你比4G多1G？</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-001.jpg" alt="5G"> </p><p>30年，从1G的大哥大，到全球规模最大的5G网络，我们的通信技术高速飞奔，我们的生活方式也彻底改变。</p><p>回忆过去的30年，你会看到一段怎样的历程？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-002.jpg" alt="5G"> </p><p>▲  中国移动 联合 星球研究所，共同出品科普视频《从1G到5G，中国经历了什么？》，用9分钟为你呈现，30年速度与激情 。</p><p>2、<a href="https://www.visualcapitalist.com/ranked-social-networks-worldwide-by-users/">排名：世界上最受欢迎的社交网络，以及谁拥有它们（截止2022年）</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-003.jpg" alt="社交"> </p><p>全球有超过45 亿人使用某种形式的社交媒体——约占全球人口的 57%。</p><p>然而，尽管社交媒体的受众广泛且多样化，但少数几家公司控制了世界上大多数最受欢迎的社交媒体平台。Meta（前身为 Facebook的科技巨头）、腾讯、Alphabet（YouTube母公司）、字节跳动等，基本垄端了社交媒体领域。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-004.jpg" alt="社交"> </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-006.jpg" alt="社交"> </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-007.jpg" alt="社交"> </p><p>▲  按月活跃用户划分的顶级社交平台</p><p>在 Top30 中，国产之光有：</p><ul><li>NO.6：微信</li><li>NO.7：快手</li><li>NO.8：TikTok</li><li>NO.10：QQ空间</li><li>NO.11：QQ  </li><li>NO.12：微博  </li><li>NO.13：抖音  </li><li>NO.21：百度贴吧</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/linggan-005.jpg" alt="社交"> </p><p>▲  十亿用户俱乐部（月活跃用户数）</p><p>其中，不得不说字节跳动，凭借其 TikTok 与抖音，在大社交历史星河中冉冉升起。</p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://blog.bitsrc.io/javascript-under-the-hood-advanced-concepts-developers-should-know-a89ddbb11228">高级概念：关于JavaScript引擎如何工作</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-001.jpg" alt="JavaScript"></p><p>目前是 2022 年，JavaScript 已经接管了互联网。人们涌向库和框架，希望寻找有关于 JavaScript 有价值的东西，研究其内部工作原理，并希望成为前10% 的  JavaScript 开发者。</p><p>本文介绍了关于 JavaScript 引擎的工作过程。</p><p>2、<a href="https://medium.com/flutter/introducing-flutter-3-5eb69151622f">Flutter 3 介绍（发布）</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-002.jpg" alt="Flutter"></p><p>Flutter 3.0 重磅发布，横跨 iOS、Android、Windows 等六大平台。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-003.jpg" alt="Flutter"></p><p>Flutter 是 Google 发布的一个用于创建跨平台、高性能移动应用的框架，简而言之，Flutter 是一套构建用户界面（UI）的工具包，开源、免费、背靠大山（Google）。</p><p>3、<a href="https://web.dev/state-of-css-2022/">CSS 2022 现状</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/article-001.jpg" alt="CSS"></p><p>Google IO 2022 演讲上关于当今和未来 Web 样式功能的介绍。</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://effidit.qq.com/">Effidit：腾讯出品的智能创作助手</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-003.jpg" alt="Effidit"></p><p>智能创作助手 Effidit（Efficient and Intelligent Editing） 是由腾讯 AI Lab 开发的一个研究性原型系统，探索用 AI 技术提升写作者的写作效率和创作体验。 </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-004.jpg" alt="Effidit"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-005.jpg" alt="Effidit"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-006.jpg" alt="Effidit"></p><p>创作辅助能力包括但不仅限于：</p><ul><li>短语补全</li><li>句子补全</li><li>智能纠错</li><li>短语润色</li><li>句子润色</li><li>例句推荐</li><li>跨语言例句检索</li><li>论文检索</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-007.jpg" alt="Effidit"></p><p>我简单尝试了功能，智能创作助手 Effidit 的纠错、润色、补全、推荐等能力均已非常出色，基本可以成为我写文章的审查润色助手了。</p><p>另外，目前其实已经有很多 AI 平台 ，都已推出具备撰稿写作、剧本生成等相关能力的 AI 机器人，所以我猜测 <strong>AI作家</strong> 未来会横行天下，特别是文案写手行业，有可能最先被智能写作平台机器人替代、并淘汰出局。</p><p>2、<a href="https://github.com/zonemeen/musicn">musicn： 一个下载高品质音乐的命令行工具 </a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-001.jpg" alt="musicn"></p><p>🎵 一个下载高品质音乐的命令行工具，其中音乐来源于<strong>咪咕</strong>（API 是从公开的网络中获得）。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-002.jpg" alt="musicn"></p><p>说明：</p><ul><li>部分歌曲支持无损音乐，支持格式：flac、mp3</li><li>优先搜索下载高品质音乐（无损 -&gt; 320K -&gt; 128K）</li><li>暂时只支持下载咪咕平台上已有的音乐</li><li>在 windows 的 git Bash 中不支持显示下载进度条并且不支持上下切换选歌，问题是 cli-progress 不兼容</li><li>node 版本 &gt; 14</li></ul><p>3、<a href="https://github.com/AykutSarac/jsonvisio.com">json visio：JSON 数据可视化工具</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-008.jpg" alt="jsonvisio"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/tools-009.jpg" alt="jsonvisio"></p><p>Github 上一款用于 json 数据的可视化工具，支持导入导出，以及简单两种样式展示，目前 star 4.1k。</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://guoyu.mirror.xyz/RD-xkpoxasAU7x5MIJmiCX4gll3Cs0pAd5iM258S1Ek">Web3 DApp 最佳编程实践指南</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-005.jpg" alt="DApp"></p><p>文章将会涉及到开发一个 DApp （<strong>去中心化应用程序</strong>）所涵盖的几乎所有方面内容。包括：</p><ul><li>认识 DApp 技术栈</li><li>智能合约编码</li><li>开发工作流与单元测试</li><li>前端与客户端开发</li><li>开发、测试与生产环境调试</li><li>服务端编码与集成</li><li>合约部署方案 L1s &amp; L2</li><li>去中心化储存方案</li><li>附录</li></ul><p>2、<a href="https://kps.hashnode.dev/learn-go-the-complete-course">Go语言学习：完全教程</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-004.jpg" alt="Go"></p><p>本文详细介绍了 Go 编程语言的基础知识和高级特性。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-006.jpg" alt="Go"></p><p>3、<a href="https://github.com/Vonng/ddia">《Designing Data-Intensive Applications》 (中文翻译电子书)</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/source-007.jpg" alt="设计数据密集型应用"></p><p>豆瓣 9.7 分，个人多次推荐，一本讨论数据系统设计实现的经典，也是学习分布式系统设计的辅助读物。</p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/442836946">生活的无限可能：一个硅谷程序员的飞行梦</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/boke-001.jpeg" alt="一个硅谷程序员的飞行梦"></p><p>本周推荐一期关于谷歌程序员学习开飞机的经历，与编程关联不大，可以当口水听，如果你也有小愿望，说不定能触动你。</p><p><strong>节目导航</strong></p><ul><li>01:00 起因？</li><li>04:00 怎么下定决心要学</li><li>05:00 安全问题？</li><li>13:00 demo flight</li><li>14:00 选学校/教练</li><li>15:00 费用问题</li><li>17:15 学开飞机有什么要求？</li><li>18:10 上课的体验</li><li>24:00 开飞机上天的流程</li><li>30:30 未来计划</li><li>35:35 一些建议</li></ul><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://vgod.medium.com/list/e731a8ecf20d">软件工程师的修炼与成长系列</a>（中文繁体）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-7/post-001.jpg" alt="软件工程師的修炼与成長系列"></p><p>软件工程师的修炼与成长系列文摘来自 vgod’s blog，目前暂出 10 篇，文摘内容干货十足，值得慢慢品味。</p><ul><li> (1) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-1-7b906f837d74">程式設計 → 軟體工程</a></li><li> (2) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-2-ac7c98d40ad6">規模與複雜度</a></li><li> (3) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-3-oncall%E7%9A%84%E6%8C%91%E6%88%B0-4d7cc8e4529e">Oncall的挑戰</a></li><li> (4) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-4-product-vs-infrastructure-384bf6acbbfb">Product vs Infrastructure</a></li><li> (5) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-5-1-1%E8%A9%B2%E8%AB%87%E4%BB%80%E9%BA%BC%E6%89%8D%E8%83%BD%E8%AE%93%E8%81%B7%E6%B6%AF%E8%B5%B7%E9%A3%9B-eaa10f2df56e">1:1該談什麼才能讓職涯起飛?</a></li><li> (6) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-6-%E6%8F%9B%E5%9C%98%E9%9A%8A-x-2-4ae7d88ee7fa">換團隊 x 2</a></li><li> (7) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-7-%E5%A6%82%E4%BD%95%E7%AA%81%E7%A0%B4%E8%B3%87%E6%B7%B1%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E5%A4%A9%E8%8A%B1%E6%9D%BF-98a1f4326a30">如何突破資深工程師的天花板</a></li><li> (8) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-8-%E8%AE%93%E8%87%AA%E5%B7%B1%E5%8F%AF%E4%BB%A5%E8%A2%AB%E5%8F%96%E4%BB%A3-61e0e2bce857">讓自己可以被取代</a></li><li> (9) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-9-%E9%81%B8%E6%93%87%E6%9C%80%E9%81%A9%E5%90%88%E8%87%AA%E5%B7%B1%E7%9A%84%E5%85%AC%E5%8F%B8-bbf12aee64f9">選擇適合自己的公司</a></li><li> (10) — <a href="https://vgod.medium.com/%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E4%BF%AE%E7%85%89%E8%88%87%E6%88%90%E9%95%B7-10-%E5%9B%9B%E7%B6%AD%E7%9A%84%E6%8A%80%E8%A1%93%E8%83%BD%E5%8A%9B-1602882aec33">四維的技術能力</a></li></ul><p>看到如此优质的文摘，我也是先 Mark 为敬，喜出望外。</p><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p><strong>人是被习惯所塑造的，优异的结果来自于良好的习惯，而非一时的行动。</strong></p><p>– 亚里士多德，摘自古川武士<a href="https://book.douban.com/subject/26771587/">《坚持，一种可以养成的习惯》</a> 中译版，第166页</p><h2 id="首发订阅"><a href="#首发订阅" class="headerlink" title="首发订阅"></a>首发订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li></ul><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description></item><item><title>无聊科技正经事周刊（第6期）：纯粹的程序员与必然的中年危机</title><category>科技周刊</category><category>程序员</category><category>中年危机</category><pubDate>Tue, 10 May 2022 17:05:08 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/05/11/weekly-6/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/cover.jpg" alt="封面图"></p><p>詹姆斯韦伯太空望远镜 <strong>有望</strong> 彻底改变我们对 <strong>遥远行星</strong> 和 <strong>深层时间</strong> 的理解。（<a href="https://www.quantamagazine.org/why-nasas-james-webb-space-telescope-matters-so-much-20211203/">from Quanta 杂志.</a>）</p><p>👉 作为一枚太空幻想者，我极力推荐你们关注 <a href="https://www.quantamagazine.org/">Quanta 杂志</a>。</p><h2 id="本周话题：纯粹的程序员与必然的中年危机"><a href="#本周话题：纯粹的程序员与必然的中年危机" class="headerlink" title="本周话题：纯粹的程序员与必然的中年危机"></a>本周话题：纯粹的程序员与必然的中年危机</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/topic-001.jpg" alt="纯粹的程序员与必然的中年危机"></p><p>持续限制的形势， 行业依然大萧条，许多盼头消失殆尽，许多程序员人至中年，本周简单发散文字，罗列探讨之。</p><ul><li><strong>不要高估技术，在个人职业生涯中的重要性，纯粹擅长技术，必然迎来中年危机。</strong></li></ul><p>技术固然重要，但是当你裹着它越过敲门砖之后，它还能伴你走多远？除非继续往学术方向单纯做研究，否则在职场上你必须是多面体，只擅长技术迟早会被重写覆盖，管理和沟通等维度的短板，势必会成为你向上突破的瓶颈。只有基于技术，组合其它能力，才能更好地提高自己的稀缺性，最好，站在高点多维度审视自己。</p><ul><li><strong>除了技术才华和集中力之外，耐力可能是你需要重视的地方。</strong></li></ul><p>技术是门体力活，光靠有限的才华和短暂的集中力，往往不够，身体耐力会是你打技术持久战的基础，最典型的反例是很多戛然而止的才华人物，必须警惕耐力，慎之又慎。 </p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://interestingengineering.com/hyundai-4x4-robotic-legs-production">韩国现代汽车已经开始生产带有机器人腿的新型 4x4 车辆</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-001.jpg" alt="现代"></p><p>▲ Elevate 概念于 2019 年在内华达州拉斯维加斯举行的消费电子展上亮相。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-002.jpg" alt="现代"></p><p>2022 年 5 月 5 日，韩国现代汽车新视野工作室博兹曼研发中心在蒙大拿州博兹曼开幕。现代新视野工作室 (NHS) 是一家专注于终极机动车辆 (UMV) 开发的部门，将在蒙大拿州博兹曼开设一个新的研究、开发和实验室中心。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-003.jpg" alt="现代"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-004.jpg" alt="现代"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-005.jpg" alt="现代"></p><p>初步计划：</p><ul><li>未来五年计划投资目标为 2000 万美元</li><li>全新的研究、开发和实验室中心利用机器人和轮式运动技术重新定义了车辆的移动性 </li><li>工作室位于蒙大拿州立大学的创新校区，将在蒙大拿州创造约 50 多个工作岗位</li></ul><p>2、<a href="https://arstechnica.com/gadgets/2022/05/apple-google-and-microsoft-want-bluetooth-proximity-to-replace-the-password/">苹果、谷歌和微软想用“Passkey”标准杀死密码</a>（英文）</p><p>2022 年 5 月 5 日，为了使网络更加安全和可供所有人使用，Apple、Google 和 Microsoft （主要的操作系统供应商）宣布计划扩大对由 FIDO 联盟和万维网联盟创建的通用无密码登录标准的支持。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-006.jpg" alt="Passkey"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-007.jpg" alt="Passkey"></p><p>该标准被称为“多设备 FIDO 凭证”或仅称为“密码”。这个新方案不是一长串字符（密码），而是让您正在登录的应用程序或网站向您的手机推送请求以进行身份验证。您需要解锁手机，使用某种密码或生物识别进行身份验证，然后您就可以访问了。对于任何设置了基于电话的双因素身份验证的人来说，这听起来像是一个熟悉的系统，但这是密码的替代品，而不是额外的因素。</p><p>虽然实现这一目标一直很艰难，但是我个人对于实现 “无密码” 的计划特别支持，我们在全网的密码太多了，如果让我们记住越来越多的密码，那会增加很多负担。</p><p>因此，没有密码，就不会忘记密码。</p><p>像面部识别、指纹识别，比 1Password 或手机验证码，体验上可舒服多了，你觉得呢？</p><p>3、<a href="https://www.thedetroitbureau.com/2022/05/mercedes-benz-says-self-driving-option-ready-to-roll/">梅赛德斯-奔驰表示 3 级自动驾驶配置已蓄势待发</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-008.jpg" alt="梅赛德斯"></p><p>梅赛德斯-奔驰汽车（Mercedes-Benz Cars）表示，计划于 5 月 17 日在德国发布用于为 S 级和 EQS 提供自动驾驶服务的条件自动化 3 级系统 “Drive Pilot”，使梅赛德斯成为世界上第一家拥有国际有效认证的自动驾驶系统的汽车制造商。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-010.jpg" alt="梅赛德斯"></p><p>当然，特斯拉预计将在 2022 年底之前准备好 “四级” 系统，甚至鼓励特斯拉车主加入正在进行的系统 “Beta 测试” 。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-009.jpg" alt="梅赛德斯"></p><p>我猜测，梅赛德斯是小步快跑，率先上市；而特斯拉则可能基于技术领先，希望一步到位。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/keji-011.jpg" alt="梅赛德斯"></p><p>自动驾驶汽车的发展分类来自汽车工程师协会 (SAE)，描述了汽车能够和可能接管驾驶员任务的程度，自动驾驶的级别范围从完全没有辅助系统的 0 级到描述完全自动驾驶的 5 级。</p><ul><li>0 级：无自动化</li><li>1 级：驾驶员辅助</li><li>2 级：部分自动化</li><li>3 级：条件自动化</li><li>4 级：高度自动化</li><li>5 级：完全自动化</li></ul><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://www.visualcapitalist.com/cp/how-mobile-phone-market-has-evolved-since-1993/">动画：手机市场 30 年的演变历程</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/linggan-001.jpg" alt="现代"></p><p>1993年，摩托罗拉占据了手机市场的一半以上。但到 2021 年，其市场份额已缩水至仅 2.2%。这是如何发生的，移动行业在过去 30 年中发生了怎样的变化？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/linggan-002.jpg" alt="现代"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/linggan-003.jpg" alt="现代"></p><p>2、<a href="https://codepen.io/gabriellewee/pen/KKQwydY">纯CSS生成的神奇宝贝头像</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/linggan-004.jpg" alt="现代"></p><p>Gabrielle Wee 为每个神奇宝贝创建了令人印象深刻、响应迅速的纯 CSS 图标。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/linggan-005.jpg" alt="现代"></p><p>这个设计灵感可以应用在我们网站系统中，用于生成或自定义头像，既灵活也轻量。 </p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://hackernoon.com/what-to-expect-from-ai-in-2022">2022 年对人工智能的期望</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-001.jpg" alt="2022年对人工智能的期望"></p><p>人工智能之所以吸引人，是因为它有无限的想象。</p><p>作者认为人工智能是一种过于复杂和动态的技术，需要有一种新的管理方法来明确来区分管理生命周期的不同阶段，以保证数据、人工智能和云技术能够在各个阶段无缝结合，创建出一个灵活而强大的系统，体现出人工智能的价值。</p><p>关于人工智能主要从三方面进行探讨：</p><ul><li>建模和仿真将释放人工智能在供应链、元宇宙以及其他领域的潜力</li><li>人工智能可以估计和预测全部成本与价值收益，而不仅仅是节省成本</li><li>人工智能太重要了（敏感数据、关键决策、业务行动..），无法由人工智能专家管理</li></ul><p>2、<a href="https://codersblock.com/blog/deep-dive-into-text-wrapping-and-word-breaking/">深入研究文本换行和分词</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-002.jpg" alt="深入研究文本换行和分词"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-003.jpg" alt="深入研究文本换行和分词"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-004.jpg" alt="深入研究文本换行和分词"></p><p>本文探索了各种控制文本在网页上换行（或不换行）以及分词的方式，值得收藏一看。</p><p>3、<a href="https://www.infoworld.com/article/3660069/will-javascript-containers-overtake-linux-containers.html">JavaScript 容器会超越 Linux 容器吗？</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-005.jpg" alt="Linux"></p><p>大多数服务器程序是 Linux 程序。它们包括一个文件系统、一些可执行文件，可能还有一些共享库，它们可能与 systemd 或 nsswitch 等系统软件交互。</p><p>Docker 普及了 Linux 容器的使用，操作系统级虚拟化，为分发服务器软件提供了一种极好的机制，每个容器镜像都是一个无依赖的可立即运行的软件包。由于服务器软件通常依赖于许多系统资源和配置，因此在过去部署它一直充满挑战，而 Linux 容器解决了这个问题。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-006.jpg" alt="JavaScript"></p><p>Node.js 和 Deno 的创建者 Ryan Dahl 认为，可以通过使用 JavaScript 容器而不是 Linux 容器来简化大多数 Web 服务。JavaScript 是通用脚本语言，由于 JavaScript 的普遍性，正在出现一种新的类似容器的抽象来简化服务器。</p><p>👉 更多关于 <a href="https://tinyclouds.org/javascript_containers">JavaScript 容器</a>。</p><p>4、<a href="https://kevincox.ca/2022/05/06/rss-feed-best-practices/">RSS源的最佳实践</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-007.jpg" alt="JavaScript"></p><p>本文全面介绍了 RSS 源的最佳实践，包括格式、内容、日期、分页、安全性等内容，并推荐我们使用 RSS 2 或 Atom 。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-008.jpg" alt="JavaScript"></p><p>当然如果你跟我一样拥有个人网站，或许选择托管第三方会更加方便管理、或者使用邮箱订阅更加符合目前的阅读习惯。</p><p>❤️ 欢迎订阅下面的Feed，您可以及时跟踪我的更新：</p><p><a href="http://feeds.feedburner.com/panshenlian/DxHj">http://feeds.feedburner.com/panshenlian/DxHj</a></p><p>（正式 Feed，大陆地区被屏蔽）</p><p><a href="https://www.panshenlian.com/atom.xml">https://www.panshenlian.com/atom.xml</a></p><p>（ 大陆 Feed，内地正常访问）</p><p>👉 延伸阅读：<a href="https://mp.weixin.qq.com/s/VUhz2Tg08UqYSAZB6nU9MQ">《RSS二十年》</a></p><p>5、<a href="https://www.developer.tech.gov.sg/singapore-government-tech-stack/overview/index.html">新加坡政府技术栈</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-009.jpg" alt="新加坡政府技术栈"></p><p>GovTech 正在开发新加坡政府技术堆栈 (SGTS)，其愿景是使整个政府 (WOG) 的开发实践现代化，以帮助构建能够响应不断变化的公民和业务需求的安全系统。为此需要采取三管齐下的方法： 人、平台和实践。</p><ul><li>人员：建立正确的能力和成长心态，以便在开发团队中采用技术实践。</li><li>平台：通过 SGTS 提供正确的工具集，用于构建高质量、安全的系统。</li><li>实践：提供有关如何使用这些工具来授权开发人员的指导。</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-010.jpg" alt="新加坡政府技术栈"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-011.jpg" alt="新加坡政府技术栈"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/article-012.jpg" alt="新加坡政府技术栈"></p><p>SGTS 是一套平台工具，可简化和简化开发过程，并支持跨 WOG 重用代码以构建安全、高质量的应用程序。SGTS 是一套 2 层架构，其中基础层专注于标准化开发工具和环境，而在顶部，服务层推动 WOG 内的代码可重用性。</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://planby.netlify.app/">Planby：基于 React 的时间线组件</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/tools-001.jpg" alt="Planby"></p><p>Planby 是一个基于 React 的组件，用于快速实现时间线组件。</p><p>2、<a href="https://www.11ty.dev/">Eleventy：一个更简单的静态站点生成器</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/tools-005.jpg" alt="Eleventy"></p><p>Eleventy 是一个简单的静态站点生成器。它可以将不同类型的模板目录转换为 HTML。 Eleventy 适用于 HTML、Markdown、JavaScript、Liquid、Nunjucks、Handlebars、Mustache、EJS、Haml 和 Pug，提供了使用 Eleventy 构建的站点示例。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/tools-002.jpg" alt="Eleventy"></p><p>▲  eslint.org </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/tools-003.jpg" alt="Eleventy"></p><p>▲ developer.chrome.com</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/tools-004.jpg" alt="Eleventy"></p><p>▲ BUILT WITH ELEVENTY </p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://petargyurov.com/bookshelf/">我的书架</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/source-001.jpg" alt="Planby"></p><p>作者起初是为了养成阅读习惯，后面发现创建一个 <strong>虚拟书架</strong> 可能是一个有趣的练习，于是通过纯 CSS 和 HTML，以及  Javascript 添加随机效果，构建了一个虚拟书架效果。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/source-002.jpg" alt="Planby"></p><p>代码开源，你可以在 <a href="https://github.com/petargyurov/virtual-bookshelf">GitHub</a> 获取。</p><p>2、<a href="https://www.odditycentral.com/technology/speech2face-an-ai-that-can-guess-what-someone-looks-like-just-by-their-voice.html">Speech2Face——一种可以通过声音猜出某人长相的人工智能</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/source-003.jpg" alt="Speech2Face"></p><p>Speech2Face 是由麻省理工学院的科学家开发的一种先进的 <strong>神经网络</strong>，经过训练可以识别某些面部特征并通过聆听人们的声音来重建人脸。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/source-004.jpg" alt="Speech2Face"></p><p>该模型旨在揭示训练数据中说话者的面部特征和声音之间存在的统计相关性，使用的训练数据是来自 YouTube 的教育视频集合，并不能笼统地代表整个世界人口。因此，与任何机器学习模型一样，该模型会受到这种数据分布不均的影响。</p><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://www.odditycentral.com/travel/this-wooden-shack-in-the-middle-of-a-desert-is-the-worlds-most-remote-post-office.html">世界上最孤独的主题邮局——腾格里沙漠木制邮局（只有15平米 ，带来沙漠深处的问候）</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/photo-001.jpg" alt="腾格里沙漠木制邮局"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/photo-002.jpg" alt="腾格里沙漠木制邮局"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/photo-003.jpg" alt="腾格里沙漠木制邮局"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/photo-004.jpg" alt="腾格里沙漠木制邮局"> </p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/528146737">我们都是《摩登时代》的打工人</a> （付费）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/boke-001.jpg" alt="摩登时代"></p><p>本期，我推荐一期「黑水公园」的付费节目：我们都是《摩登时代》的打工人。</p><p>让我们重温一下《摩登时代》的经典场景：优雅的查理（卓别林饰）、工人流水线拧螺丝、钢铁洪流、上班不如蹲监狱、工作压力、发疯、进入精神病院…</p><p>过去的经济大萧条与生存危机，今天是否历历在目？</p><p>当然，收听需要准备好 4 块大洋，重温这讽刺与现实。</p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://www.raychase.net/6965">软件工程师成长的一个误区</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/post-001.jpg" alt="软件工程师成长的一个误区"></p><p>关于软件工程师成长的一个 <strong>误区</strong> ：“我对技术感兴趣，我只想做技术，走技术路线。”</p><p>这句话是不是很听过呢？请不要误会，这句话本身没有问题，但是说出这句话的软件工程师中，十有八九有一个误区，就是 <strong>高估了技术本身在个人职业生涯中，起作用的占比</strong>。</p><p>而本文作者，也曾经是其中之一。</p><p>软件工程师成长成长误区的个中原因，其实并不难理解。软件工程师，不是搞学术，而是搞工程，而工程能力，是一个非常复杂的多面体。</p><p>2、<a href="https://blog.pragmaticengineer.com/the-product-minded-engineer/">以产品为中心的软件工程师</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/post-002.jpg" alt="以产品为中心的软件工程师"></p><p>本文介绍了具有 <strong>产品意识</strong> 的软件工程师的关键特征，以及可以从哪些方面增强软件工程师的产品意识。</p><ul><li>积极主动地提出产品创意/意见</li><li>对业务、用户行为和数据产生兴趣</li><li>好奇心驱动和多关心 “为什么”</li><li>成为强大的沟通者和与非工程师的良好关系</li><li>预先权衡产品与工程</li><li>边缘情况的妥善处理</li><li>快速的产品验证周期</li><li>端到端产品功能所有方面</li><li>通过反复的学习循环产生强烈的产品直觉</li></ul><p>3、<a href="https://zhuanlan.zhihu.com/p/468644178">Facebook 工程师文化独特之处</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-6/post-003.jpg" alt="Facebook工程师文化独特之处"></p><p>作者在 Facebook 工作了 7 年，结合 Facebook 之前和之后的其它公司的经验，他觉得 Facebook 的文化有些 <strong>独特</strong> 的地方值得分享，并总结了三个主题：</p><ul><li>工程师对产品结果负责任</li><li>基础架构被视为内部产品</li><li>救火比防火更容易获得回报</li></ul><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p><strong>我们仍在等待死亡的治愈，但在此之前，创造产品可能是下一个最好的选择。</strong></p><p>– <a href="https://hackernoon.com/the-case-for-work-being-the-meaning-of-life">《工作就是生命的意义》</a></p><p>2、</p><p><strong>所有东西的 90% 都是垃圾。如果你觉得自己不喜欢歌剧，浪漫小说，抖音，乡村音乐，素食，NFTs，那你可以试试看自己能否找出不是垃圾的 10%。</strong></p><p>– <a href="https://justinyan.me/post/4911">《凯文·凯利70岁生日写的103条人生忠告》（中文翻译）</a></p><p>3、</p><p><strong>自信地发言就像你是对的一样，仔细的聆听就像你是错的一样。</strong></p><p>– <a href="https://justinyan.me/post/4911">《凯文·凯利70岁生日写的103条人生忠告》（中文翻译）</a></p><h2 id="首发订阅"><a href="#首发订阅" class="headerlink" title="首发订阅"></a>首发订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/weekly">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li></ul><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description></item><item><title>母亲节#超人妈妈们辛苦了</title><category>母亲节</category><pubDate>Sun, 8 May 2022 02:10:18 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/05/08/live-005-mom-s-day/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<iframe src="https://www.panshenlian.com/video/2022/listen-mom-everything.mp4" width="100%" height="600px" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"> </iframe><p>（本篇完）</p>]]></content:encoded><description>&lt;iframe src="https://www.panshenlian.com/video/2022/listen-mom-everything.mp4" 
width="100%" height="600px" scrolling="no" border="0" frameb</description></item><item><title>无聊科技正经事周刊（第5期）：五一长假与虚拟旅行</title><category>旅游</category><category>科技周刊</category><category>虚拟现实</category><category>增强现实</category><category>假期</category><pubDate>Tue, 3 May 2022 16:01:12 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/05/04/weekly-5/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/cover.jpg" alt="cover"></p><p>日本铁路公司（ JR 西日本）将使用巨型人形机器人（Gundam）修电车线（<a href="https://newatlas.com/robotics/jr-west-gundam-maintenance-robot/">via</a>）。</p><h2 id="本周话题：五一长假与虚拟旅行"><a href="#本周话题：五一长假与虚拟旅行" class="headerlink" title="本周话题：五一长假与虚拟旅行"></a>本周话题：五一长假与虚拟旅行</h2><p>本周恰好是五一长假，依目前居家建议和限制措施来看，理智的我们不会奢求来一场旅行。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-001.jpg" alt="topic"></p><p>(<a href="https://wwwnc.cdc.gov/travel/page/travel-to-the-extreme">via</a>)</p><p>于是我开始思考一个问题，既然大家无法选择出行，那居家的我们能否来一场 <strong>虚拟旅行</strong> 呢？</p><p>简而言之，我们能否在家里，通过各种 <strong>虚拟现实技术</strong> 或 <strong>增强现实技术</strong> 等（后面统称虚拟技术）来实现旅行的目的呢？</p><p>答案是：<strong>可以</strong>。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-002.jpg" alt="topic"></p><p>(<a href="https://arpost.co/2020/12/30/study-surging-interest-virtual-travel/">via</a>)</p><p>其实从 2019 年以来，虚拟技术一直在迅猛发展，特别是在当前这种特殊形势之下，虚拟技术越来越凸显其价值与意义，特别能够适应、满足人们对于虚拟现实的诉求。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-003.jpg" alt="topic"></p><p>2020 年全球虚拟现实市场规模为 44.2 亿美元，预计到 2027 年 VR 行业的市场规模将达到 575.5 亿美元左右。（当然以上是北美的统计）</p><p>虚拟技术为客户提供模拟体验，目前已在 <strong>零售、医疗、房地产、汽车、游戏、旅游、娱乐以及艺术展馆</strong> 等行业提供了广泛的应用。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-004.jpg" alt="topic"></p><p>▲ 地产行业的虚拟现实 360° 全景看房  (<a href="https://www.51cto.com/article/662708.html">via</a>)</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-005.jpg" alt="topic"></p><p>▲ 游戏  (<a href="http://www.mingyexin.com/news/1498.cshtml">via</a>)</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-006.jpg" alt="topic"></p><p>▲ 百度街景（<a href="http://zhidao.baidu.com/question/2116255582723781227.html">via</a>）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/topic-007.jpg" alt="topic"></p><p>▲ 汽车  (<a href="https://showcase.rextheme.com/car-showroom-virtual-tour/">via</a>)</p><p>当然，在旅游行业也同样可以融合应用这些技术。</p><p>不过，最近我发现一类网站应用，虽然没有使用到任何虚拟技术或增强技术，仅仅是通过视频录制，便能够让人身临其境，享受非凡的虚拟假期。</p><p>▼例如国外这个叫 <a href="https://virtualvacation.us/">Virtual Vacation（虚拟假期）</a>  的网站。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-010.jpg" alt="虚拟假期"></p><p>虚拟假期这个网站的视频，来自世界各地的 POV 视频合集，汇集了许多 <strong>步行、驾驶、远足和其他视角</strong> 视频，人们在其中手持相机探索独特的地方。</p><p>你可以置身其中，随机挑选一座城市，挑选一个视角，特别有趣。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-008.jpg" alt="虚拟假期"></p><p>▲ 不同国家</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-009.jpg" alt="虚拟假期"></p><p>▲ 不同城市</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-car.jpg" alt="虚拟假期"></p><p>▲ 我在纽约公路上开车驰骋</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-walk.jpg" alt="虚拟假期"></p><p>▲ 在随机一座城市街道漫步</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-tour.jpg" alt="虚拟假期"></p><p>▲ 往巴黎铁塔悠走片刻 </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-home.jpg" alt="虚拟假期"></p><p>▲ 在一处建筑内参观</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-fly.jpg" alt="虚拟假期"></p><p>▲ 坐飞机返程</p><p>可想而知，如果再运用虚拟技术、增强技术与视频相结合，那体验必然会更加强烈。</p><p>因此，<strong>居家旅行</strong> 是可行的，特别是现如今异常波动的大环境下，资本和市场的无限驱动，5G 技术的成熟应用、软硬件设备的持续升级、虚拟现实技术（元宇宙）等领域的不断探索延伸，未来许多行业都将发生翻天覆地的变革，可能到最后我们只需要一部手机、一个遥控或一个音箱，就能畅享我们日常中的无限需求场景。</p><p>无论如何，虚拟技术是未来不可分割的一部分，我们必须拥抱它。</p><h3 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h3><p>你也来试试  <a href="https://virtualvacation.us/">Virtual Vacation（虚拟假期）</a>  吧，选一座你想去的城市，选一种你喜欢的出行方式，带上耳机，感受这个过程，相信你会喜欢。（我疯狂玩了1个多小时..）</p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://japantoday.com/category/tech/japan-mulls-programs-to-develop-talent-in-semiconductors-batteries">日本考虑培养半导体、电池人才的计划</a> （英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-000.jpg" alt="日本考虑培养半导体"></p><p>（日本标准时间 2022年5月3日 上午 06:00 发布）东京政府消息人士称，日本正在考虑建立涉及全国工业、学术界和政府部门的人才发展计划，以促进国内 <strong>半导体</strong> 和 <strong>电池</strong> 的发展。</p><p>显然，芯片和电动汽车，核心就对应着半导体和电池。在目前全球经济看似下行的状况下，芯片和电动汽车需求仍保持新增长，为避免被卡脖子，各国之间对该领域人才和技术的审视真的是慎之又慎。</p><p>2、<a href="https://techxplore.com/news/2022-04-lithium-ion-battery-minutes-anode.html">研究人员在5.6分钟内将锂离子电池充电至60%</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-001.jpg" alt="研究人员在5.6分钟内将锂离子电池充电至60%"></p><p>电动汽车比汽油动力汽车更环保，但它们尚未被广泛采用，阻碍这种转变的一个问题是 <strong>电动汽车充电需要很长时间</strong>。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-002.jpg" alt="研究人员在5.6分钟内将锂离子电池充电至60%"></p><p>最近（2022 年 4 月 27 日），中国多个机构的研究人员通过 <strong>在其阳极上添加铜涂层和纳米线以改善排序</strong>，从而提高了锂离子电池的充电速度。他们的研究发表在《科学进展》杂志上，研究文章为 <a href="https://www.science.org/doi/10.1126/sciadv.abm6624">《双梯度结构设计实现极快充电的锂离子电池》</a> 。</p><p>加速充电的主要瓶颈之一是 <strong>电池的阳极</strong>，大多数由石墨制成，并采用无序浆料构成，研究人员指出，这不是通过电流的有效方式，除了其中的材料排列方式之外，还有它们之间的间隙大小问题。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-003.jpg" alt="研究人员在5.6分钟内将锂离子电池充电至60%"></p><p>快充锂离子电池对于缩短电动汽车的充电时间是非常可取的，但它受到石墨负极较差的倍率性能的阻碍，在研究文章中，研究人员提出了一种石墨负极颗粒尺寸和电极孔隙率双梯度结构设计，用于在严格的电极条件下实现极快的充电锂离子电池。</p><p>3、<a href="https://www.wired.com/story/ddos-attack-botnet-crypto-platform/">有史以来最强大的 DDoS 攻击之一袭击了 CloudFlare</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-004.jpg" alt="CloudFlare"></p><p>Cloudflare 最近被黑客用 1530 万个请求轰炸它，因此受到了有史以来最大的分布式拒绝服务攻击。</p><p>DDoS 攻击可以通过多种方式进行，包括 <strong>数据量</strong>、<strong>数据包数</strong> 或 <strong>每秒发送的请求数</strong>。经过分析，Cloudflare 记录到每秒 3.4 TB 的容量 DDoS 攻击（试图消耗目标可用的带宽资源）和每秒 8.09 亿个数据包和每秒1720 万个请求，后两种攻击通过对应用层攻击，试图耗尽目标基础设施的计算资源。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-006.jpg" alt="CloudFlare"></p><p>另外，Cloudflare 表示，负责攻击的僵尸网络由大约 6,000 个机器人目标组成，每秒发送的有效载荷高达 1000 万个请求，此次共计来自 112 个国家，其中约 15% 的火力来自印度尼西亚，其次是俄罗斯、巴西、印度、哥伦比亚和美国。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-005.jpg" alt="CloudFlare"></p><p>特别是，攻击者很聪明，通过控制云托管服务提供商上被感染的服务器进行攻击，其中一些正在运行基于 Java 的应用程序，这是值得注意的，因为最近发现了一个安全漏洞 ( <a href="https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/">CVE-2022-21449: Psychic Signatures in Java，数字签名算法重大漏洞</a> )，该漏洞可用于在各种基于 Java 的应用程序中绕过身份验证，让攻击者轻松伪造 TLS 证书和签名、双因素身份验证消息以及由一系列广泛使用的开放式系统生成的授权凭证标准。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-007.jpg" alt="CloudFlare"></p><p>▲ 当然，如果很多朋友跟我一样依然在使用 Java 8，那么恭喜你，很安全。目前该安全漏洞仅对 Java 15-18 有影响，可以通过安装 <a href="https://www.oracle.com/security-alerts/cpuapr2022.html">2022 年 4 月重要补丁</a> 更新修复程序即可。</p><p>4、<a href="https://www.theverge.com/2022/4/28/23043011/snapchat-pixy-drone-hands-on?scrolla=5eb6d68b7fedc32c19ef33b4">Snapchat 的飞行相机</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-008.jpg" alt="CloudFlare"></p><p>Snap 发布了一款名为 Pixy 的相机无人机。无人机仅重 101 克，体积小到可以放入裤子口袋，它可以跟随用户四处拍摄照片或视频。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-009.jpg" alt="CloudFlare"></p><p>Pixy 的电池可以持续 5 到 8 次飞行，每次飞行大约 10 到 20 秒。素材以无线方式同步到 Snapchat 进行编辑，并可直接在应用程序或其他地方共享，文章中提供了解释 Pixy 的视频。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-010.jpg" alt="CloudFlare"></p><p>▲ 当然，大疆多年来也一直在制造小型无人机，并且具有更长的电池寿命和更高质量的视频，不过会更加昂贵，而且操作相对复杂。</p><p>5、<a href="https://www.theverge.com/2022/4/25/23037875/meta-retail-store-burlingame-ray-ban-stories-portal-quest-hours-location">Meta将于下个月开设第一家零售店</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-013.jpg" alt="Meta"></p><p>Meta 的第一家商店于 5 月 9 日在其位于加利福尼亚州伯林盖姆的校园内开业，它被称为Meta Store。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-011.jpg" alt="Meta"></p><p>它相当小，约为 1500 平方英尺，但允许购物者试用产品，包括：</p><ul><li>Meta Quest 2 虚拟现实耳机</li><li>Ray-Ban Stories 智能眼镜</li><li>Portal 可视电话</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-012.jpg" alt="Meta"></p><p><strong>为什么需要开设实体店？</strong></p><p>扎克希望元宇宙是巨大的，但是当面对现实世界时，这很难卖。</p><p>一项调查发现 48% 的青少年对虚拟世界不确定或不感兴趣，而另一项调查发现只有 16% 的美国人可以正确描述它。</p><p>所以 Meta Store 将尝试通过亲身体验、精心策划的体验来吸引元矛盾。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/keji-014.jpg" alt="Meta"></p><p><strong>跟风</strong></p><p>其实，谷歌在纽约市的第一家商店设有模拟客厅，用于试用 Nest 设备，以及用于测试其 Pixel 智能手机摄像头的低光选项的房间，但谷歌最近仍然宣布将在纽约开设第二家商店。</p><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://edm.com/gear-tech/artist-plugs-mushrooms-into-synthesizer-to-make-electronic-music">蘑菇电信号制作出悦耳的灵魂乐曲</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-001.jpg" alt="蘑菇电信号制作出悦耳的灵魂乐曲"></p><p>蘑菇并不总是因为它们的致幻作用而被使用，它们也被用来 <strong>创作音乐</strong>。</p><p>艺术家兼作曲家塔伦·纳亚尔（Tarun Nayar）以现代生物学的绰号创作和演奏音乐，他将蘑菇插入合成器，并用真菌产生的电波创作音乐。为了实现这种声音，他将电极放入蘑菇中并将它们连接到他的音板，让电信号流过植物并触发合成器，然后将它们变成人类可以听到的声音。</p><p>Nayar 还用西瓜、可可、芒果和仙人掌作曲。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-003.jpg" alt="蘑菇电信号制作出悦耳的灵魂乐曲"></p><p>▲ 这种从蘑菇和其它物种中转换电波的独特能力为 Nayar 提供了足够的能量，并制作成专辑 <a href="https://open.spotify.com/album/203hWYS6NNyT9pmNq2OZRb">《植物音乐第 2 卷：夏威夷的水果和花朵》</a>，这张专辑利用了大自然的随机性，真正让听众踏上了一段声音之旅，不受人类通常处理音乐制作的标准方法的限制，或许你可以听一听。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-002.jpg" alt="蘑菇电信号制作出悦耳的灵魂乐曲"></p><p>▲ 当然，你也可以到 <a href="https://www.tiktok.com/@modernbiology/video/7079909922363739398">TikTok</a> 上听到更多现代生物学自然生成的音乐。</p><p>2、<a href="https://www.expo2025.or.jp/overview/design_system/">大阪·关西世博会发布了设计系统</a>（日文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-004.jpg" alt="设计系统"></p><p>2025年日本万国博览会协会制定了 “EXPO 2025设计体系” ，用于2025年日本万国博览会（大阪/关西博览会）。</p><p>该设计系统旨在统一世博会的各种界面，并提供超越模拟和数字界限的一致体验。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-005.jpg" alt="设计系统"></p><p>▲ 「人与科技共创未来设计」这个设计系统是通过结合人手元素和技术元素来设计的，以质疑 “未来人与技术共创” 的理想方式，也标志 CELL（细胞）从诞生到 CELL（细胞）重复分裂增殖，以致生命生长进化。</p><p>为了将这样一个生命系统的理想方式形象化，首先设计了一个算法，该设计系统可以同时实现可应用于 “Virtual Expo” 等虚拟空间的 3D 建模和作为平面设计开发的 2D 平面外观。此外，不仅通过技术元素完成设计，还通过人工设计人员进行参数调整和选择模型等最终调整，实现了人为干预的设计过程，这是一个涉及人和机器的共同创造设计过程。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-006.jpg" alt="设计系统"></p><p>▲ 大阪/关西世博会的设计系统表达了“当没有开始或结束的时候，以及生命的流动”。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-007.jpg" alt="设计系统"></p><p>▲ 构成 “EXPO 2025设计体系” 的设计元素由三大元素设计而成。</p><ul><li>代表个体生命的 “ID”</li><li>代表由多种生命组成的社区的 “GROUP” </li><li>代表多种生命共存的生态系统的 “WORLD” </li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-008.jpg" alt="设计系统"></p><p>▲ <strong>ID —— 成长和进化的“个人生活”</strong></p><p>“ID”是设计元素的最小单位，主要是在表达“个体”时使用的元素。这个ID象征着“个体生命”，并根据生命的成长阶段变化成各种形式。生命的每一个过程都是经过设计的：</p><ul><li>成为原始细胞的状态（Lives）</li><li>成长阶段（Growth）</li><li>进化成各种生命形式的阶段（Evolution） </li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-009.jpg" alt="设计系统"></p><p>▲ <strong>Group ——汇聚不同生活，共创未来的“社区”</strong>  </p><p>代表社区的设计元素“组”是不同颜色和形状的 ID 的集合。这个 GROUP象征着“共创未来社会”，这是大阪-关西世博会的重要元素，表达了社区“设计未来社会”的每一个过程：</p><ul><li>不同的人相遇（Join）</li><li>敞开心扉讨论（Sync）</li><li>开始与未来一起前进（Act）</li></ul><p>思想是通过将个人与开放联系起来而产生的，我们将做出改变，创造更美好的未来。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-010.jpg" alt="设计系统"></p><p>▲ <strong>World —— 多元生活融为一体并产生共鸣的生态系统</strong></p><p>象征未来生态系统的设计元素“WORLD” 。正如我们每个人看到的世界是多种多样的，这个元素也有各种各样的面部表情。通过自由裁剪一个大的核心图形，它可以扩展成无数的应用程序。此外，WORLD 的外观也不在其中。它有四个场景：</p><ul><li>Inochi（生命）</li><li>Umi（海洋）</li><li>Noyama（野山）</li><li>Hikari（光）</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/linggan-011.jpg" alt="设计系统"></p><p>日本的设计一直很超前，本次  “EXPO 2025设计体系”  融合艺术、科技、人文、未来等等，让人耳目一新。</p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://www.pzuraq.com/blog/four-eras-of-javascript-frameworks">JavaScript 框架的四个时代</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-003.jpg" alt="Bun"></p><p>作者回顾了过去几十年的 JavaScript 开发历史，认为我们可以大致将 JavaScript 分为四个主要时代：</p><ul><li>早期时代（jQuery，MooTools ..）</li><li>第一个框架（Backbone.js，Angular 1，Ember.js ..）</li><li>以组件为中心的视图层（React.js，Vue.js，Svelte ..）</li><li>全栈框架（Next.js ，Nuxt.js ，SvelteKit，Astro ..）</li></ul><p>每个时代都有自己的主题和中心冲突，在每一个时代，都吸取了作为一个社区的重要经验教训，并且缓慢而坚定地前进。从最初的轻量库集成，到如今成熟的工程化解决方案， JavaScript 社区正朝着正确的方向前进。</p><p>内容详实细腻，值得一看。</p><p>2、<a href="https://news.ycombinator.com/item?id=31152148">为什么 npm 生态系统似乎比 PHP 更容易受到供应链攻击？</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-004.jpg" alt="Bun"></p><p>这是 Hacker News 上的一个问答，有一位网友认为其中一个很重要的原因，是由于 JS 生态系统拥有比其他语言更好的扩展性开发者体验，像 Java/Python/Dart/Ruby（或 C++，如果您包含 dpkg 和 brew 之类的工具）依赖管理器会遭受通常称为 “依赖地狱” 的问题，或者在某些特定情况下称为菱形依赖问题。它们从根本上 <strong>无法扩展</strong>，并且很容易遇到 <strong>包冲突</strong>，而 npm 方案（后来被 Rust 和 Go 采用）允许安装具有任意大依赖树的依赖项，不必担心诸如 Java 的依赖传播或命名空间冲突之类的黑客攻击（至少在大多数情况下），不过人们可能会抱怨 JS 依赖管理器的上游安全问题 ”。</p><p>问答主要针对依赖面进行分析，更多讨论可以详细见回答。</p><p>3、<a href="https://redis.com/blog/redis-7-generally-available/">Redis 7.0 发布！</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-007.jpg" alt="Redis"></p><p>Redis 7.0几乎包括了对其各个方面的增量改进，对其管理的几乎每个子系统都进行了多项改进，包括内存、计算、网络和存储。最值得注意的是 Redis Functions、ACLv2、命令自省和 Sharded Pub/Sub，它们代表了基于用户反馈和生产经验教训的现有功能的重大演变。</p><p>7.0 版添加了近 50 个新命令和选项来支持这种演变并扩展 Redis 的现有功能。例如，位图、列表、集合、排序集合和流数据类型都添加了支持其数据管理用例的功能。</p><p>简而言之，新版本的发布使 Redis 更高效、更稳定和更精简。</p><p>4、<a href="https://krausefx.com//blog/how-i-put-my-whole-life-into-a-single-database">我如何将我的一生都放在一个数据库中</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/article-003.jpg" alt="我如何将我的一生都放在一个数据库中"></p><p>作者在过去的两年半中，每天都跟踪 100 多种不同的数据类型——从健身和营养到社交生活、计算机使用和天气。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/article-004.jpg" alt="我如何将我的一生都放在一个数据库中"></p><p>而项目的目标是回答关于他生活的问题，比如：</p><ul><li>生活在不同的城市对健康、生产力和幸福感等其他因素有何影响？</li><li>睡眠如何影响我的一天、我的健康水平和幸福感？</li><li>天气和不同的季节如何影响我的生活？</li><li>过去几年有什么趋势吗？</li><li>计算机时间、工作和会议时间如何影响我的个人生活？</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/article-005.jpg" alt="我如何将我的一生都放在一个数据库中"></p><p>项目 <a href="https://howisfelix.today/">howisfelix.today</a> 总共收集了超过 380,000个数据点，展示了 42 个图表，并且100% 完全开源。</p><p>我认为这是很有趣的一件事，特别是持之以恒的记录，其实存在很大挑战。所以这个项目是否在一些方面，能让你萌生想法？</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://codepen.io/davidkpiano/pen/gOoNZNe">Linear-style Cursor Glow（线性风格的光标发光特效）</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/tools-001.jpg" alt="线性风格的光标发光特效"></p><p>David Khourshid 使用 CSS 和一些 JavaScript 创建了一个可爱的小运动效果，添加一个光标发光，照亮了当前鼠标悬停的元素，其中主要使用 CSS 变量、径向渐变和插图重新创建技术。</p><p>2、<a href="https://bun.uptrace.dev/">Bun：一款 Go 语言轻量级 ORM </a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-001.jpg" alt="Bun"></p><p>Bun 是一个适用于 PostgreSQL、MySQL 和 SQLite 的简单且性能良好的 ORM。</p><p>Bun 是 Go 的一个 SQL-first 的数据库客户端。SQL-first 意味着大多数 SQL 查询可以自动编译为 Bun 表达式，而 Bun 表达式看起来和感觉就像 SQL 查询。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-002.jpg" alt="Bun"></p><p>Bun 的目的是允许使用古老的SQL编写查询，并帮助将结果扫描成常见的 Go 类型：structs、maps、slices 和  columns。</p><p><strong>主要特点是：</strong></p><ul><li>适用于 PostgreSQL、 MySQL、 SQLite</li><li>选择 map、struct、slice of maps/structs/vars</li><li>批量插入</li><li>使用公用表表达式批量更新</li><li>批量删除</li><li>Fixtures</li><li>迁移</li><li>软删除</li></ul><p>3、<a href="https://ebiten-zh.vercel.app/">Ebiten：Go语言开发的简单2D游戏引擎</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/tools-002.jpg" alt="Ebiten"></p><p>Ebiten 是一款由 Go 语言开发的开源游戏引擎，Ebiten 的简单 API 可以让您的 2D 游戏开发更加简单快捷，并支持同时发布到多个平台。</p><p>4、<a href="https://github.com/anuraghazra/github-readme-stats">github-readme-stats：动态生成的github统计信息</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/tools-003.jpg" alt="github-readme-stats"></p><p>一款为您的 github 动态生成统计信息的小插件。</p><p>5、<a href="https://frappe.io/gantt">Frappe Gantt：开源 JavaScript 甘特图控件</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/tools-004.jpg" alt="Gantt"></p><p>一个简单的、交互式的、现代的网络甘特图库，具有拖动、调整大小、依赖关系和时间尺度。</p><p>6、<a href="https://github.com/bbc/peaks.js">Peaks 2.0：用于与音频波形交互的 UI 组件</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/tools-005.jpg" alt="Peaks"></p><p>Peaks 是一个 JavaScript 客户端组件，用于在浏览器中显示音频波形并与音频波形交互。</p><p>7、<a href="https://github.com/missive/emoji-mart">Emoji Mart 5.0：可定制的 web 表情选择器</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/tools-006.png" alt="Emoji"></p><p>Emoji Mart是一个可定制的 web 表情选择器 HTML 组件。</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://easyai.tech/">产品经理的人工智能学习库</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-005.jpg" alt="人工智能学习库"></p><p>产品经理的人工智能学习库，用通俗易懂的方式帮助产品经理快速学习人工智能相关知识，让非技术人群也能快速理解人工智能。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/source-006.jpg" alt="人工智能学习库"></p><p>内容包含：机器学习、深度学习、NLP、CV、语音交互、人工智能算法、人工智能算力等。</p><p>2、Java 系列文章（英文）</p><ul><li><p><a href="https://learncsdesign.medium.com/microservices-observability-design-patterns-bdfa5807f81e">微服务可观察性设计模式</a></p></li><li><p><a href="https://jaxenter.com/hunter-performance-regressions-176316.html">使用开源 Hunter 检测性能回归</a></p></li><li><p><a href="https://www.infoq.com/news/2022/01/apple-gcgc-java/">Apple 开源 GCGC：分析 Java GC 日志的工具</a></p></li><li><p><a href="https://krzysztofslusarski.github.io/2021/08/22/cont-longtts.html">使用 async-profiler 持续分析 “很长时间才能到达安全点” 问题？</a></p></li></ul><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://www.odditycentral.com/animals/youtuber-creates-custom-fish-tank-so-he-can-take-his-goldfish-on-walks.html">遛鱼——移动水族缸</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-001.jpg" alt="移动水族缸"></p><p>台湾手工达人（黄小杰）在网上发布了一段展示他发明的视频  ( <a href="https://www.youtube.com/watch?v=_fblqVypzQA">YouTube</a> ) ，称这是他迄今为止最艰巨的挑战之一——遛鱼。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-002.jpg" alt="移动水族缸"></p><p>这款遛鱼水族缸，通过焊接了一个金属底盘作为密封亚克力管的支撑，将其放在轮子上，并添加了一个用于推动的把手。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-003.jpg" alt="移动水族缸"></p><p>当然，足智多谋的网友纷纷献策：想在夜间炫耀鱼，可以再配电池供电的过滤系统、氧气泵和照明系统；另外还可以安装，减震系统，让鱼更舒服。</p><p>▼ 其实，在去年就有一家日本公司开发了一种奇特的活鱼容器状袋子，既适合想要带着自己喜欢的鱼散步的宠物主人，也适合超级新鲜生鱼片的粉丝。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-004.jpg" alt="移动水族缸"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-005.jpg" alt="移动水族缸"></p><p>2、<a href="https://www.rmg.co.uk/whats-on/astronomy-photographer-year/galleries/2021-overall-winners">2021 年天文摄影师的壮观获奖照片</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-006.jpg" alt="天文摄影"></p><p>▲ 《年度总冠军》</p><p>凭借这张令人着迷的照片《金戒指》，董书昌获得了年度天文摄影师的。该图像显示了2020 年 6 月 21 日在西藏阿里地区拍摄的日环食。</p><p>评委们称赞该图像结合了天文摄影的科学、艺术和技术独创性。评委史蒂夫·马什（Steve Marsh）称其为“喜怒无常、宁静、完美捕捉和专业处理。你感觉好像你可以伸手到天空，把它放在你的手指上。”</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-007.jpg" alt="天文摄影"></p><p>▲ 《极光冠军》</p><p>Dmitrii Rybalka（俄罗斯）的极光舞蹈</p><p>“我对这张照片很感兴趣。它既美丽又令人不安。鲜活的绿色与船的墨蓝色并置，密密麻麻，几乎像天鹅绒一样。然而，在这艘平稳移动的船上，没有任何人类生命的迹象，感觉就像一部科幻电影的开场。”</p><p>– Sue Prichard，竞赛评委和格林威治皇家博物馆艺术高级策展人</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-008.jpg" alt="天文摄影"></p><p>▲ 《星系冠军》</p><p>钟武（中国）的乳环</p><p>“这个宇宙圈是我们今年看到的最令人叹为观止的条目之一。颜色的平衡，从戒指的上半部分发光，到更暗、更喜怒无常的下半部分，似乎包含了整个范围的威严和美丽。摄影师花了将近两年的时间才拼凑出这张照片，他的奉献精神也必须受到赞扬。”</p><p>– Imad Ahmed，比赛评委兼新月会理事</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-009.jpg" alt="天文摄影"></p><p>▲ 《月亮冠军》</p><p>Nicolas Lefaudeux (法国)超越肢体</p><p>“这就是太阳系在太空旅行者眼中的样子。宇宙距离和天体可以从一个新的视角在一张图像中看到。” </p><p>– László Francsics，竞赛评委和匈牙利天文摄影师协会主席</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-010.jpg" alt="天文摄影"></p><p>▲ 《人与空间冠军》</p><p>Deepal  Ratnayaka（英国）的封锁</p><p>“这张照片让我情绪激动，因为它是我们人类在这场大流行期间共同经历的美丽快照——被限制在我们的四堵墙内，若有所思地凝视着窗外，满怀希望。一幅美丽动人的画面。”</p><p>– Melissa Brobby，物理研究所竞赛评委和社交媒体官</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-011.jpg" alt="天文摄影"></p><p>▲ 《行星、彗星和小行星冠军》</p><p>Frank Kuszaj（美国）的彩色象限仪流星 </p><p>“彩色流星的独特图像。毫无疑问，这是最幸运的捕捉之一，但与此同时，图像背后还有一个绝对惊人和鼓舞人心的故事。努力工作，一些运气和出色的图像处理能力。”</p><p>– Yuri Beletsky，比赛评委和夜景摄影师</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-012.jpg" alt="天文摄影"></p><p>▲ 《天际线赢家》</p><p>Jeffrey Lovelace（美国）的Luna Dunes</p><p>“这张照片是我今年最喜欢的照片之一。它由沙子和天空中的微妙纹理组成，与新月井相得益彰。我也喜欢这些颜色——海军蓝和金色——给这张照片增添了一种高贵的感觉。”</p><p>– 伊马德·艾哈迈德</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-015.jpg" alt="天文摄影"></p><p>▲《星星和星云获胜者》</p><p>加州梦 NGC 1499  ，特里·汉考克（英国）</p><p>“在捕获和处理这张图像时做出的颜色选择本身就足够漂亮，但它们不仅仅是为了展示，而是揭示了更多关于星云本身的信息。一个绝妙的想法和绝妙的执行。”</p><p>– Steve Marsh，BBC Sky at Night 杂志的比赛评委和艺术编辑</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-014.jpg" alt="天文摄影"></p><p>▲《年度青年天文摄影师获奖者》</p><p>太阳系 全家福 至璞王，15岁（中国）</p><p>“所有的家庭都是非传统的，所以我喜欢这个围绕太阳系的新鲜、温和有趣的概念。一部来自天文摄影新星的有意义的、技术成熟的作品。”</p><p>– 苏·普里查德</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/photo-013.jpg" alt="天文摄影"></p><p>▲《最佳新人奖》</p><p>猎鹰 9 号飞越月球作者 Paul Eckhardt（美国）</p><p>“正确的目标、正确的时机、正确的构图。这三个方面使这张图像成为众多天文照片和许多航天器过境月球图像中的特殊一张。这张照片是无法计划好的——这是一位才华横溢的摄影师良好的情境意识的结果。”</p><p>– 拉斯洛弗朗西斯</p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/yule/6688726/198784423">去中心化时代的奇幻漂流</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/boke-001.jpg" alt="移动水族缸"></p><p>本期推荐一档轻松点的节目，《得意忘形》播客的第 53 期《去中心化时代的奇幻漂流》，畅谈思考与改变，聊各自的人生困惑、灵性体验、创造之心，以及对下一个时代的生产关系和组织形态的探索与感受。</p><p><strong>时长接近 3 小时，花了我 3 天晨跑时间才大致听完，启发不少思考。</strong></p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://thehustle.co/04252022/">红绿灯的未来</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/article-001.jpg" alt="红绿灯的未来"></p><p>红绿灯在 19 世纪的英格兰首次使用，但煤气灯很危险，容易爆炸，到了 1914 年，克利夫兰安装了第一个电动交通信号灯，经典的三色灯于 1920 年在底特律出现。</p><p>而到了今天，已经可以通过人工智能，帮助为交通信号灯计时，以改善应急响应、拥堵和公共交通等等。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/article-002.jpg" alt="红绿灯的未来"></p><p>本文简单介绍了 <a href="https://lyt.ai/">LYT</a> （一家智能互联交通技术提供商）通过使用数据和机器学习来操纵交通信号并改善交通流量。</p><p>2、<a href="https://www.nytimes.com/2022/01/05/technology/personaltech/tech-2022-vr-metaverse.html">2022年将侵入我们生活的技术</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/post-001.jpg" alt="2022年将侵入我们生活的技术"></p><p>许多技术趋势一次又一次的出现，因为简单地说，技术需要很长时间才能成熟应用，然后我们大多数人才会真正想购买拥有它们。然而，已日渐成熟的技术正在逐步推动市场，让一切来得更快。</p><p>其中可能包括你最近经常听到并且势不可挡的，例如 <strong>元宇宙</strong>、<strong>智能家居</strong>、<strong>连接健康</strong> 以及 <strong>电动车</strong>。</p><p>3、<a href="https://hackaday.com/2022/04/30/new-tech-and-the-old-ways/">新技术和旧方法</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-5/post-002.jpg" alt="新技术和旧方法"></p><p>Mush 与我们分享了一个将老式模拟摄影打印与现代 LCD 屏幕相结合的项目。</p><p>基本思想是使用 4K 单色屏幕代替负片，通过将屏幕直接放在相纸上并在均匀光源下曝光来进行接触式打印，就像旧方式一样，但改用 LCD 而不是胶片。</p><p>通过尝试开箱即用的东西（新技术），将新工具应用于旧程序，Muth 偶然发现了新功能，作为黑客，我们一直在玩弄我们可以随时掌握的最新技术。一旦这么做，您可能还会偶然发现新技术提供的更多新的可能性，豁然开朗！</p><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p>不要生气，笑着做每一件事，做你喜欢做的事。</p><p>我喜欢在公司工作，不要有敌人，感恩，平静地生活。</p><p><strong>生命只是地球上的一个通道，享受它，做你想做的事</strong>。</p><p>– <a href="https://www.odditycentral.com/news/worlds-most-loyal-employee-has-been-working-for-the-same-company-for-84-years.html">《世界上最忠诚的员工在同一家公司工作了84年》</a></p><p>2、</p><p>好的代码既有实质又有风格，它提供了所有必要的信息，没有多余的细节，它绕过了低效率和错误，它准确、简洁、严谨，足以被人类阅读和理解。</p><p>但是当今计算机科学面临的挑战不能仅靠 <strong>良好的设计</strong> 来克服，编程语言和系统设计需要 <strong>设计思想</strong>。</p><p>– <a href="https://www.quantamagazine.org/barbara-liskov-is-the-architect-of-modern-algorithms-20191120/">《现代算法的架构师》</a> (Barbara Liskov , 2008年图灵奖得主，2004年冯诺依曼奖得主，2018 年被授予计算机先锋奖以表彰她一生的成就)</p><h2 id="首发订阅"><a href="#首发订阅" class="headerlink" title="首发订阅"></a>首发订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/weekly">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li></ul><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description><enclosure length="-1" type="application/activity+json" url="https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/"/><itunes:explicit/><itunes:subtitle>这里记录每周值得分享的科技内容，周三发布。 本刊开源（GitHub: senlypan/weekly），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</itunes:subtitle><itunes:summary>这里记录每周值得分享的科技内容，周三发布。 本刊开源（GitHub: senlypan/weekly），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</itunes:summary><itunes:keywords>旅游, 科技周刊, 虚拟现实, 增强现实, 假期</itunes:keywords></item><item><title>二〇二二，四月掠影</title><category>生活</category><pubDate>Sun, 1 May 2022 08:25:21 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/05/01/live-004-april-day-in-2022/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/live/april-day/park-002.jpg"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>二〇二二年四月，匆匆终结，极为丰满，从一号准备孩子的四岁生日会开始，就隐约有种忙碌预感，直到刚刚在公园被疫情防控安保人员撵走，我们一家三口拖着帐篷、书籍、电脑、零食、玩具，顶着三个 26°C 的太阳，走进一家丹尼斯七天地星巴克，坐下，咖啡厅舒缓的音乐，让神经很快松弛，缓过神，想想今天既狼狈又好笑，趁着孩子酣睡，打开电脑，回忆四月。</p><h2 id="二〇二二年四月一日"><a href="#二〇二二年四月一日" class="headerlink" title="二〇二二年四月一日"></a>二〇二二年四月一日</h2><p>四月一日，恰好孩子农历四岁生日（我们都习惯过农历的生日），我和老婆提前一天晚上就张罗布置，至凌晨两点。早晨刚起床，抱着孩子看现场布置，他罕见说道：“布置太过简单”，他本能的说辞让我感到惊讶，这种惊讶就在那么一瞬间，让你察觉到孩子在突然生长。</p><p>当天我提前下班回家，匆忙加工现场，好让他晚上招呼小客人，参加生日派对。</p><p>晚上邀请的小客人们陆续到齐，妈妈便统一接待上楼，几位长辈开始围着他们转。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/party-001.jpg"></p><p>我有意识的让他当主人，他还是不擅长接待客人，从一位小客人手里抢过一个自己珍爱的玩具，即使当晚收到很多小客人送的礼物。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/party-003.jpg"></p><p>不过，他们玩得很开心。</p><h2 id="二〇二二年四月四日"><a href="#二〇二二年四月四日" class="headerlink" title="二〇二二年四月四日"></a>二〇二二年四月四日</h2><p>清明节假期，幼儿园布置作业，孩子分配到一个 <strong>小鱼对对碰</strong> 的游戏制作，这款游戏主要考验孩子对方向、顺序、颜色的区别判断。</p><p>完美主义的我，时常让我陷入苦恼，过得一点不轻松，特别是对于孩子的每一个作业，我都希望尽善尽美，对现实过于理想化，往往不好。当然，孩子妈妈恰恰与我相反，所以她放手让我去折腾，不干涉，不过问，不参与，但其实偶尔闲暇之余会帮忙打下手。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/homework-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/homework-002.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/homework-003.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/homework-004.jpg"></p><p>作品一共做了一天一夜，通宵至凌晨 5 点半方才完成，期间昏昏欲睡，精力完全游离，状态很糟糕，好在拖着疲惫完成了。隔天补觉占用了半天，反正对自己坚持的做事标准，从来不后悔付出，只是往往身体会有些吃亏罢了。</p><h2 id="二〇二二年四月十日"><a href="#二〇二二年四月十日" class="headerlink" title="二〇二二年四月十日"></a>二〇二二年四月十日</h2><p>疫情下的郑州，大家足不出远门，便就近消遣，公园野外开始热闹起来，又值舒朗四月天，自然，都想换个地方睡觉，当天晴空万里，爽朗得让人愉悦。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/park-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/park-002.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/park-003.jpg"></p><p>躺在吊床里，自然看天空，树荫下没有刺眼的阳光，鸟叫声让人精神放松。</p><h2 id="二〇二二年四月十六日"><a href="#二〇二二年四月十六日" class="headerlink" title="二〇二二年四月十六日"></a>二〇二二年四月十六日</h2><p>四月中旬，这座城市疫情加重了，还好在管控之内，大家长舒一口气，检测全民核酸，好像这就是原始的生活状态，并没有特别惊讶，反倒是心疼医护人员、社区工作者、志愿者。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/collection-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/collection-002.jpg"></p><p>他们这一代，有着特殊生活形态，在他们脑海里，核酸检测是一件太正常不过的事。</p><h2 id="二〇二二年四月三十日"><a href="#二〇二二年四月三十日" class="headerlink" title="二〇二二年四月三十日"></a>二〇二二年四月三十日</h2><p>昨天是四月最后一天，也是五一长假第一天，孩子盼着去博物院，最主要是好奇心。我上网看到消息说假期对外开放，有几个场次，9点-11点、11点-14点、14-16点，我便早早预约了11点场。</p><p>毕竟是假期第一天，现场人特别多，居多是家长领着孩子，像四岁这般年纪去的，我只看见零星几个。我猜想他们都认为博物馆是个大型游乐城，所以我提前做好心理准备，孩子会不喜欢。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-002.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-003.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-004.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-005.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-006.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-007.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/museum-008.jpg"></p><p>果然不出意料，孩子对夏商周的一切历史文明和出土文物，一点不在乎，反倒是对着一楼的残疾人自动电梯两眼放光，不顾一切跑过去坐了几趟，玩的不亦乐乎，好在没耽误着急的残疾人使用，不然就真的罪过了。</p><p>但夏商周的礼乐文明，确实让人叹为观止。</p><p>孩子回家后只记住博物馆两个印象：<strong>国宝冰淇淋</strong> 和 <strong>残疾人自动电梯</strong>。</p><h2 id="二〇二二年五月一日"><a href="#二〇二二年五月一日" class="headerlink" title="二〇二二年五月一日"></a>二〇二二年五月一日</h2><p>此时坐在星巴克，草草记录四月时光流水账，孩子睡得香甜，刚刚我们才被疫情管控安保人员从公园了驱离出来，听说是附近出现了疫情，周围三个公园都拉起了警戒线，避免人群扎堆，所以原本准备去帐篷中偷个清懒的我，无奈收拾露营装备径直来到丹尼斯七天地星巴克。</p><p><img src="https://www.panshenlian.com/images/post/live/april-day/camping-001.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/camping-002.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/camping-003.jpg"></p><p><img src="https://www.panshenlian.com/images/post/live/april-day/sleep-001.jpg"></p><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>四月份从下旬开始晨跑，计算本月跑过的距离，九天，一共是五十公里，超过一个全马（42.195公里）的距离。跑步是一项要求简单的运动，一个人就能完成，甚至不需要做热身，不过我不想一下透支身体，我跑不了太长的距离，限制自己最多 6 公里，基本在 35 分钟，跑步时没有痛苦的感觉，反倒是每日让大脑放空的时间。</p><p>特别是最近很多想法，都是在跑步时想到的。</p><p>目前看来，跑步让我的生活进入一个良性循环，因为每日需要 6 点半起床，因此便倒逼着我前一晚必须 23 点前入睡，加上午休 40 分钟，身体逐渐适应这个节奏。</p><p>另外，聊聊写作，本周发表了几篇作品，在某些网站进入了创作者排行榜，当然欣喜，但并不太放在心上。</p><p>起身收拾，准备回家，回忆了近两小时。</p><p>五月见。</p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/live/april-day/park-002.jpg"&gt;&lt;/p&gt;
&lt;h2 id="前言"&gt;&lt;a href="#前言" class="headerlink" title="前</description></item><item><title>【架构视角】一篇文章带你彻底吃透Spring</title><category>AOP</category><category>一文读懂</category><category>Spring</category><category>IOC</category><category>反射</category><pubDate>Wed, 27 Apr 2022 14:33:12 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/04/27/spring-001-spring-core/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/spring-core-cover.jpg" alt="spring-core-cover"></p><h2 id="框架的意义"><a href="#框架的意义" class="headerlink" title="框架的意义"></a>框架的意义</h2><p>对于程序员来说，我们通常知道很多概念，例如组件、模块、系统、框架、架构等，而本文我们重点说 <strong>框架</strong>。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-000.jpg" alt="02-spring-000"></p><ul><li><strong>框架</strong>，本质上是一些实用经验集合。即是前辈们在实际开发过程中积攒下来的实战经验，累积成一套实用工具，避免你在开发过程中重复去造轮子，特别是帮你把日常中能遇到的场景或问题都给屏蔽掉，框架的意义在于屏蔽掉开发的基础复杂度、屏蔽掉此类共性的东西，同时建立严格的编码规范，让框架使用者开箱即用，并且只需要关注差异面，即业务层面的实现。简而言之，框架只干一件事，那就是 <strong>简化开发</strong>。然后在此基础上，可能会再考虑一些安全性、效率、性能、弹性、管理、拓展、解耦等等。</li></ul><h2 id="理解-Spring-核心"><a href="#理解-Spring-核心" class="headerlink" title="理解 Spring 核心"></a>理解 Spring 核心</h2><p>Spring 作为一个框架，目的也是：<strong>简化开发</strong> ，只不过在简化开发的过程中 Spring 做了一个特别的设计，那就是 <strong>Bean管理</strong>，这也是 Spring 的设计核心，而 Bean 生命周期管理的设计巧妙的 <strong>解耦</strong> 了 Bean 之间的关系。</p><p>因此 Spring 核心特性就是 <strong>解耦</strong> 和 <strong>简化</strong>。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-018.jpg" alt="02-spring-000"></p><p>Spring 框架图示展示得很清晰，基本描绘出 Spring 框架的核心：</p><ul><li><strong>内核</strong></li><li><strong>外延</strong></li></ul><p>简单说，就是 Spring 设计了一个 <strong>核心容器</strong> Core Container，这里头主要就是管理 Bean 生命周期，然后为了服务这些业务 Bean ，引入了 Core , Context , SpEL 等工具到核心容器中。然后在核心容器基础上，又为了把更多的能力集成进来，例如为了拓展 <strong>数据访问</strong> 能力加入了 JDBC 、ORM 、OXM 、JMS 、Transactions 等，为了拓展 <strong>Web</strong> 能力加入了 WebSocket 、Servlet、Web、Portlet 等，其中为了把 RequestMapping 或 Servlet 等这些使用集成到业务 Bean 上，引入了 AOP ，包括还有引入（最终是提供） Aspects、Instrumentation、Messageing 等增强方式。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-017.jpg" alt="02-spring-000"></p><p>所以仔细一看，Spring 就是把像数据库访问、Web支持、缓存、消息发送等等这些能力集成到业务 Bean 上，并提供一些测试支持。总结来说理解 Spring 就两点：</p><ol><li><p><strong>Bean管理：</strong> 解耦Bean关系。理解为内核，从 Bean 的定义、创建、管理等，这是业务Bean。</p></li><li><p><strong>功能增强：</strong> 解耦功能、声明式简化。理解为外延，在业务Bean基础上，需要访库等能力，那就是功能增强。</p></li></ol><p>基本体现的就是两个核心特性，一个 <strong>解耦</strong>、一个 <strong>简化</strong>。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-019.jpg" alt="02-spring-000"></p><p><strong>Bean管理</strong> 本身就是在做 <strong>解耦</strong>，解除耦合，这个解耦指 Bean 和 Bean 之间的关联关系，Bean 之间通过接口协议互相串联起来的，至于每个接口有多少个实现类，那都不会有任何影响，Bean 之间只保留单点通道，通过接口相互隔离，关系都交给 Spring 管理，这样就避免了实现类和实现类之间出现一些耦合，就算方法增减了、引用变更了也不至于互相污染。</p><p><strong>功能增强</strong> 本身就是在做 <strong>简化</strong>，例如声明式简化，像声明式编程，使用者只需要告诉框架他要什么，不用管框架是如何实现的。另外简化方面还有 <strong>约定优于配置</strong> （当然这个确切的说是 SpringBoot 里的设计），约定优于配置其实就是约定好了无需去做复杂的配置，例如你引入一个什么组件或能力就像 redis 或 kafka，你不需要提前配置，因为 springboot 已经为你默认配置，开箱即用。</p><blockquote><p>因此 Spring 框架特性怎么理解？就 <strong>解耦</strong> 和 <strong>简化</strong> 。 </p></blockquote><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-020.jpg" alt="02-spring-000"></p><p>而 SpringBoot，简单理解就是在 Spring 框架基础上添加了一个 <a href="https://www.baidu.com/s?wd=Service%20Provider%20Interface"><strong>SPI 可拓展机制</strong></a> 和 <strong>版本管理</strong>，让易用性更高，简化升级。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/springcloud.jpg" alt="springcloud.jpg"></p><p>而 SpringCloud，简单理解就是，由于 SpringBoot 的 <strong>依赖</strong> 可以被很好的管理，<strong>拓展</strong> 可以被可插拔的拓展，因此在 SpringBoot 基础上集成了很多跟微服务架构相关的能力，例如集成了很多组件，便有了 SpringCloud 全生态。</p><p><strong>基本了解了 Spring 特性之后，我们回到 Spring 的核心设计 IoC 与 AOP</strong> 。</p><h2 id="IoC"><a href="#IoC" class="headerlink" title="IoC"></a>IoC</h2><p>我们说了 Spring 的其一特性是 <strong>解耦</strong>，那到底是使用什么来解耦？ </p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/spring-ioc-bean.jpg" alt="02-spring-core-001"></p><p>控制反转（Inversion of Control，缩写为 <strong>IoC</strong>），是面向对象编程中的一种设计原则，可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入（Dependency Injection，简称 <strong>DI</strong>），还有一种方式叫“依赖查找”（Dependency Lookup，EJB 和 Apache Avalon 都使用这种方式）。通过控制反转，对象在被创建的时候，由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说，依赖被注入到对象中。</p><p>简单来说，就是原本 Bean 与 Bean 之间的这种互相调用，变成了由 <strong>IoC</strong> 容器去统一调配。如果没使用 <strong>IoC</strong> 容器统一管理业务 Bean，你的应用在部署、修改、迭代的时候，业务 Bean 是会侵入代码实现并互相调用的。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-021.jpg" alt="02-spring-000"></p><h3 id="那么问题来了，所有系统都需要引入-IOC-吗？"><a href="#那么问题来了，所有系统都需要引入-IOC-吗？" class="headerlink" title="那么问题来了，所有系统都需要引入 IOC 吗？"></a>那么问题来了，所有系统都需要引入 IOC 吗？</h3><p><strong>IoC</strong> 容器是面向 <strong>迭代</strong> 起作用，如果你的应用就 <strong>不存在迭代</strong> 的情况，即系统是万年不变的，那没必要引入 <strong>IoC</strong>，因为你每引入一项技术，都势必会增加复杂度，所以额外引入 <strong>IoC</strong> 也一样会增加你整体应用的复杂度，所以假如 <strong>不存在迭代</strong>，大可直接写死A类引用B类，B类又写死引用C类，无需引入 <strong>IoC</strong>。一定要理解每一项技术背后是为了解决什么问题，同时在做架构设计的时候记住两个原则：<strong>合适</strong> 、<strong>简单</strong>。当然，实际上我们大部分应用是 <strong>持续迭代</strong> 的，在类实现上、互相引用上、甚至接口协议上都有可能变化，所以一般引入 <strong>IoC</strong> 是合适的（如果是接口协议变化，即参数或返回值发生变化，那还是需要改动类间的代码的）。</p><p>具体的，<strong>IoC</strong> 相当于是把 Bean 实例的创建过程交给 Spring 管理，无论是通过 XML、JavaConfig，还是注解方式，最终都是把实例化的工作交给 Spring 负责，之后 Bean 之间通过接口相互调用，而实例化过程中就涉及到 <strong>注入</strong>，无论采用什么方式来实例化 Bean，<strong>注入</strong> 的类别就两种：</p><ul><li><strong>Setter注入</strong> ： 通过 setter 来设置，发生在对象 <strong>实例化之后</strong> 设置。</li><li><strong>构造器注入</strong> ： 通过构造器注入，发生在对象 <strong>实例化之前</strong> 就得把参数/实例准备好。</li></ul><p><strong>setter注入：</strong></p><ol><li>与传统的 JavaBean 的写法更相似，程序开发人员更容易理解、接受。通过 setter 方法设定依赖关系显得更加直观、自然。</li><li>对于复杂的依赖关系，如果采用构造注入，会导致构造器过于臃肿，难以阅读。Spring 在创建 Bean 实例时，需要同时实例化其依赖的全部实例，因而导致性能下降。而使用设值注入，则能避免这些问题。</li><li>尤其在某些成员变量可选的情况下，多参数的构造器更加笨重。</li></ol><p><strong>构造器注入：</strong></p><ol><li>构造器注入可以在构造器中决定依赖关系的注入顺序，优先依赖的优先注入。</li><li>对于依赖关系无需变化的 Bean ，构造注入更有用处。因为没有 <strong>setter</strong> 方法，所有的依赖关系全部在构造器内设定，无须担心后续的代码对依赖关系产生破坏。</li><li>依赖关系只能在构造器中设定，则只有组件的创建者才能改变组件的依赖关系，对组件的调用者而言，组件内部的依赖关系完全透明，更符合高内聚的原则。</li></ol><p>而这两种方式的注入方式都使用了 <strong>反射</strong>。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/reflect.jpg" alt="02-spring-000"></p><h2 id="反射"><a href="#反射" class="headerlink" title="反射"></a>反射</h2><p>了解反射相关类以及含义：</p><ul><li><strong>java.lang.Class：</strong> 代表整个字节码。代表一个类型，代表整个类。</li><li><strong>java.lang.reflect.Method：</strong> 代表字节码中的方法字节码。代表类中的方法。</li><li><strong>java.lang.reflect.Constructor：</strong> 代表字节码中的构造方法字节码。代表类中的构造方法。</li><li><strong>java.lang.reflect.Field：</strong> 代表字节码中的属性字节码。代表类中的成员变量（静态变量+实例变量）。</li></ul><p><strong>java.lang.reflect</strong> 包提供了许多反射类，用于获取或设置实例对象。简单来说，反射能够：</p><ol><li><strong>在运行时</strong> 判断任意一个对象所属的类；</li><li>在运行时构造任意一个类的对象；</li><li>在运行时判断任意一个类所具有的成员变量和方法；</li><li>在运行时调用任意一个对象的方法；</li><li><strong>生成动态代理</strong>。</li></ol><p><strong>IoC</strong> 和 <strong>反射</strong>，只是把 Bean 的实例创建处理完，而后续还有 <strong>功能增强</strong>，功能增强靠的就是 <strong>AOP</strong>。</p><h2 id="AOP"><a href="#AOP" class="headerlink" title="AOP"></a>AOP</h2><p>AOP全名 Aspect-Oriented Programming ，中文直译为面向切面编程，当前已经成为一种比较成熟的编程思想，可以用来很好的解决应用系统中分布于各个模块的交叉关注点问题。在轻量级的J2EE中应用开发中，使用AOP来灵活处理一些具有 <strong>横切性质</strong> 的系统级服务，如事务处理、安全检查、缓存、对象池管理等，已经成为一种非常适用的解决方案。 </p><h3 id="为什么需要AOP"><a href="#为什么需要AOP" class="headerlink" title="为什么需要AOP"></a>为什么需要AOP</h3><p>当我们要进行一些日志记录、权限控制、性能统计等时，在传统应用程序当中我们可能在需要的对象或方法中进行编码，而且比如权限控制、性能统计大部分是重复的，这样代码中就存在大量 <strong>重复代码</strong>，即使有人说我把通用部分提取出来，那必然存在调用还是存在重复，像性能统计我们可能只是在必要时才进行，在诊断完毕后要删除这些代码；还有日志记录，比如记录一些方法访问日志、数据访问日志等等，这些都会渗透到各个要访问方法中；还有权限控制，必须在方法执行开始进行审核，想想这些是多么可怕而且是多么无聊的工作。如果采用 Spring，这些日志记录、权限控制、性能统计从业务逻辑中分离出来，通过 Spring 支持的面向切面编程，在需要这些功能的地方动态添加这些功能，无需渗透到各个需要的方法或对象中；有人可能说了，我们可以使用“代理设计模式”或“包装器设计模式”，你可以使用这些，但还是需要通过编程方式来创建代理对象，还是要 <strong>耦合</strong> 这些代理对象，而采用 Spring 面向 <strong>切面</strong> 编程能提供一种更好的方式来完成上述功能，一般通过 <strong>配置</strong> 方式，而且不需要在现有代码中添加任何额外代码，现有代码专注业务逻辑。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/spring-aop.jpg" alt="02-spring-000"></p><p>所以，AOP 以横截面的方式插入到主流程中，<strong>Spring AOP 面向切面编程能帮助我们无耦合的实现：</strong></p><ul><li>性能监控，在方法调用前后记录调用时间，方法执行太长或超时报警。</li><li>缓存代理，缓存某方法的返回值，下次执行该方法时，直接从缓存里获取。</li><li>软件破解，使用 AOP 修改软件的验证类的判断逻辑。</li><li>记录日志，在方法执行前后记录系统操作日志。</li><li>工作流系统，工作流系统需要将业务代码和流程引擎代码混合在一起执行，那么我们可以使用AOP将其分离，并动态挂接业务。</li><li>权限验证，方法执行前验证是否有权限执行当前方法，没有则抛出没有权限执行异常，有业务代码捕捉。</li><li>等等</li></ul><p>AOP 其实就是从应用中划分出来了一个切面，然后在这个切面里面插入一些 <strong>“增强”</strong>，最后产生一个增加了新功能的 <strong>代理对象</strong>，注意，是代理对象，这是Spring AOP 实现的基础。这个代理对象只不过比原始对象（Bean）多了一些功能而已，比如 <strong>Bean预处理</strong>、<strong>Bean后处理</strong>、<strong>异常处理</strong> 等。 AOP 代理的目的就是 <strong>将切面织入到目标对象</strong>。 </p><h3 id="AOP如何实现"><a href="#AOP如何实现" class="headerlink" title="AOP如何实现"></a>AOP如何实现</h3><p><strong>前面我们说 IoC 的实现靠反射，然后解耦，那 AOP 靠啥实现？</strong> </p><p>AOP，简单来说就是给对象增强一些功能，我们需要看 Java 给我们预留了哪些口或者在哪些阶段，允许我们去织入某些增强功能。</p><p>我们可以从几个层面来实现AOP。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-004.png" alt="02-spring-core-004">  </p><ul><li><p><strong>编译期</strong> </p><ul><li>原理：在编译器编译之前注入源代码，源代码被编译之后的字节码自然会包含这部分注入的逻辑。</li><li>代表作如：lombok, mapstruct（编译期通过 pluggable annotation processing API 修改的）。</li></ul></li><li><p><strong>运行期，字节码加载前</strong> </p><ul><li>原理：字节码要经过 classloader（<a href="http://jvm.panshenlian.com/#/zh-cn/06-class-loader">类加载器</a>）加载，那我们可以通过 <a href="http://jvm.panshenlian.com/#/zh-cn/06-define-class-loader">自定义类加载器</a> 的方式，在字节码被自定义类加载器 <strong>加载前</strong> 给它修改掉。</li><li>代表作如：javasist, java.lang.instrument ,ASM（操纵字节码）。</li><li>许多 agent 如 Skywaking, Arthas 都是这么搞，注意区分 <strong>静态agent</strong> 与 <strong>动态agent</strong>。</li><li><strong>JVMTI</strong> 是 JVM 提供操作 native 方法的工具，<strong>Instrument</strong> 就是提供给你操纵 <strong>JVMTI</strong> 的 java 接口，详情见 <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html">java.lang.instrument.Instrumentation</a></li></ul></li><li><p><strong>运行期，字节码加载后</strong></p><ul><li>原理：字节码被类加载器加载后，动态构建字节码文件生成目标类的 <strong>子类</strong>，将切面逻辑加入到子类中。</li><li>代表作如：jdk proxy, cglib。 </li></ul></li></ul><blockquote><p>按照类别分类，基本可以理解为：</p></blockquote><table><thead><tr><th>类别</th><th>原理</th><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>静态AOP</td><td><strong>在编译期</strong>，切面直接以字节码的形式编译到目标字节码文件中</td><td>对系统无性能影响</td><td>灵活度不够</td></tr><tr><td>动态AOP</td><td><strong>在运行期</strong>，目标类加载后，为接口动态生成代理类，将切面织入到代理类中</td><td><a href="http://spring.panshenlian.com/#/zh-cn/02-java-dynamic-proxy">动态代理</a>方式，相对于静态AOP更加灵活</td><td>切入的关注点需要实现接口，对系统有一点性能影响</td></tr><tr><td>动态字节码生成</td><td><strong>在运行期</strong>，目标类加载后，动态构建字节码文件生成目标类的 <strong>子类</strong>，将切面逻辑加入到子类中</td><td>没有接口也可以织入</td><td>扩展类的实例方法为final时，则无法进行织入。性能基本是最差的，因为需要生成子类嵌套一层，spring用的cglib就是这么搞的，所以性能比较差</td></tr><tr><td>自定义类加载器</td><td><strong>在运行期</strong>，在字节码被自定义类加载器加载前，将切面逻辑加到目标字节码里，例如阿里的Pandora</td><td>可以对绝大部分类进行织入</td><td>代码中如果使用了其他类加载器，则这些类将不会被织入</td></tr><tr><td>字节码转换</td><td><strong>在运行期</strong>，所有类加载器加载字节码前，进行拦截</td><td>可以对所有类进行织入</td><td>-</td></tr></tbody></table><p><strong>当然</strong>，理论上是越早织入，性能越好，像 <strong>lombok</strong>,<strong>mapstruct</strong> 这类静态AOP，基本在编译期之前都修改完，所以性能很好，但是灵活性方面当然会比较差，获取不到运行时的一些信息情况，所以需要权衡比较。</p><h3 id="简单说明5种类别："><a href="#简单说明5种类别：" class="headerlink" title="简单说明5种类别："></a>简单说明5种类别：</h3><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/java-aop.png" alt="02-spring-000"></p><p>当然我整理了一份详细的脑图，可以直接在网页上打开。</p><p>《脑图：Java实现AOP思路》:</p><p><a href="https://www.processon.com/embed/62333d1ce0b34d074452eec2">https://www.processon.com/embed/62333d1ce0b34d074452eec2</a></p><iframe id="embed_dom" name="embed_dom" frameborder="0" style="display:block;width:100%; height:250px;" src="https://www.processon.com/embed/62333d1ce0b34d074452eec2"></iframe><h3 id="1、静态AOP"><a href="#1、静态AOP" class="headerlink" title="1、静态AOP"></a>1、静态AOP</h3><p>发生在 <strong>编译期</strong>，通过 Pluggable Annotation Processing API 修改源码。 </p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-005.jpg" alt="02-spring-core-005"></p><p>在 javac 进行编译的时候，会根据源代码生成抽象语法树（AST），而 java 通过开放 Pluggable Annotation Processing API 允许你参与修改源代码，最终生成字节码。典型的代表就是 <strong>lombok</strong>。 </p><h3 id="2、动态AOP-（动态代理）"><a href="#2、动态AOP-（动态代理）" class="headerlink" title="2、动态AOP （动态代理）"></a>2、动态AOP （<a href="http://spring.panshenlian.com/#/zh-cn/02-java-dynamic-proxy">动态代理</a>）</h3><p>发生在 <strong>运行期</strong>，于 <strong>字节码加载后</strong>，类、方法已经都被加载到方法区中了。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/spring-aop-diy.png" alt="spring-aop-diy"></p><p>典型的代表就是 <strong>JDK Proxy</strong>。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 需要代理的接口，被代理类实现的多个接口，都必须在这里定义</span></span><br><span class="line">    Class[] proxyInterface = <span class="keyword">new</span> <span class="title class_">Class</span>[]&#123;IBusiness.class,IBusiness2.class&#125;;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 构建AOP的Advice，这里需要传入业务类的实例</span></span><br><span class="line">    <span class="type">LogInvocationHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LogInvocationHandler</span>(<span class="keyword">new</span> <span class="title class_">Business</span>());</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 生成代理类的字节码加载器</span></span><br><span class="line">    <span class="type">ClassLoader</span> <span class="variable">classLoader</span> <span class="operator">=</span> DynamicProxyDemo.class.getClassLoader();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 织入器，织入代码并生成代理类</span></span><br><span class="line">    <span class="type">IBusiness2</span> <span class="variable">proxyBusiness</span> <span class="operator">=</span> </span><br><span class="line">        (IBusiness2)Proxy.newProxyInstance(classLoader, proxyInterface, handler);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 使用代理类的实例来调用方法</span></span><br><span class="line">    proxyBusiness.doSomeThing2();</span><br><span class="line">    ((IBusiness)proxyBusiness).doSomeThing();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>其中代理实现 <strong>InvocationHandler</strong> 接口，最终实现逻辑在 <strong>invoke</strong> 方法中。生成代理类之后，只要目标对象的方法被调用了，都会优先进入代理类 <strong>invoke</strong> 方法，进行增强验证等行为。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">LogInvocationHandler</span> <span class="keyword">implements</span> <span class="title class_">InvocationHandler</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> Object target;  <span class="comment">// 目标对象</span></span><br><span class="line"></span><br><span class="line">    LogInvocationHandler(Object target)&#123;</span><br><span class="line">        <span class="built_in">this</span>.target = target;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 执行原有逻辑</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">rev</span> <span class="operator">=</span> method.invoke(target,args);</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 执行织入的日志，你可以控制那些方法执行切入逻辑</span></span><br><span class="line">        <span class="keyword">if</span> (method.getName().equals(<span class="string">&quot;doSomeThing2&quot;</span>))&#123;</span><br><span class="line">            <span class="comment">// 记录日志</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> rev;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当然动态代理相对也是性能差，毕竟也多走了一层代理，每多走一层就肯定是越难以优化。</p><p>虽然，动态代理在运行期通过接口动态生成代理类，这为其带来了一定的灵活性，但这个灵活性却带来了两个问题：</p><ol><li>第一代理类必须实现一个接口，如果没实现接口会抛出一个异常。</li><li>第二性能影响，因为动态代理使用反射的机制实现的，首先反射肯定比直接调用要慢，经过测试大概每个代理类比静态代理多出10几毫秒的消耗。其次使用反射大量生成类文件可能引起 <strong>Full GC</strong> 造成性能影响，因为字节码文件加载后会存放在JVM运行时区的方法区（或者叫持久代，<strong>JDK1.8</strong> 之后已经在元空间）中，当方法区满的时候，会引起 <strong>Full GC</strong> ，所以当你大量使用动态代理时，可以将持久代设置大一些，减少 <strong>Full GC</strong> 次数。</li></ol><p>关于动态代理的详细原理和流程，推荐阅读<a href="http://spring.panshenlian.com/#/zh-cn/02-java-dynamic-proxy">《一文读懂Java动态代理》</a>。</p><h3 id="3、动态字节码生成"><a href="#3、动态字节码生成" class="headerlink" title="3、动态字节码生成"></a>3、动态字节码生成</h3><p>发生在 <strong>运行期</strong>，于 <strong>字节码加载后</strong> ，生成目标类的子类，将切面逻辑加入到子类中，所以使用Cglib实现AOP不需要基于接口。</p><p>此时类、方法同样已经都被加载到方法区中了。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/spring-aop-diy.png" alt="spring-aop-diy"></p><p>典型的代表就是 <strong>Cglib</strong>（底层也是基于ASM操作字节码）， <strong>Cglib</strong> 是一个强大的,高性能的 <strong>Code</strong> 生成类库，它可以在运行期间扩展Java类和实现Java接口，它封装了 <strong>Asm</strong>，所以使用 <strong>Cglib</strong> 前需要引入 <strong>Asm</strong> 的jar。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;   </span><br><span class="line">     byteCodeGe();   </span><br><span class="line"> &#125;   </span><br><span class="line">  </span><br><span class="line"> <span class="comment">/**  </span></span><br><span class="line"><span class="comment">  * 动态字节码生成  </span></span><br><span class="line"><span class="comment">  */</span>  </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">byteCodeGe</span><span class="params">()</span> &#123;   </span><br><span class="line">     <span class="comment">//创建一个织入器   </span></span><br><span class="line">     <span class="type">Enhancer</span> <span class="variable">enhancer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Enhancer</span>();   </span><br><span class="line">     <span class="comment">//设置父类   </span></span><br><span class="line">     enhancer.setSuperclass(Business.class);   </span><br><span class="line">     <span class="comment">//设置需要织入的逻辑   </span></span><br><span class="line">     enhancer.setCallback(<span class="keyword">new</span> <span class="title class_">LogIntercept</span>());   </span><br><span class="line">     <span class="comment">//使用织入器创建子类   </span></span><br><span class="line">     <span class="type">IBusiness2</span> <span class="variable">newBusiness</span> <span class="operator">=</span> (IBusiness2) enhancer.create();   </span><br><span class="line">     newBusiness.doSomeThing2();   </span><br><span class="line"> &#125;   </span><br><span class="line">  </span><br><span class="line"> <span class="comment">/**  </span></span><br><span class="line"><span class="comment">  * 记录日志  </span></span><br><span class="line"><span class="comment">  */</span>   </span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">LogIntercept</span> <span class="keyword">implements</span> <span class="title class_">MethodInterceptor</span> &#123;   </span><br><span class="line">  </span><br><span class="line">     <span class="meta">@Override</span>   </span><br><span class="line">     <span class="keyword">public</span> Object <span class="title function_">intercept</span><span class="params">(</span></span><br><span class="line"><span class="params">Object target, </span></span><br><span class="line"><span class="params">Method method, </span></span><br><span class="line"><span class="params">Object[] args, </span></span><br><span class="line"><span class="params">MethodProxy proxy)</span> <span class="keyword">throws</span> Throwable &#123;   </span><br><span class="line">         </span><br><span class="line"><span class="comment">//执行原有逻辑，注意这里是invokeSuper   </span></span><br><span class="line">         <span class="type">Object</span> <span class="variable">rev</span> <span class="operator">=</span> proxy.invokeSuper(target, args);   </span><br><span class="line">         <span class="comment">//执行织入的日志   </span></span><br><span class="line">         <span class="keyword">if</span> (method.getName().equals(<span class="string">&quot;doSomeThing&quot;</span>)) &#123;   </span><br><span class="line">             System.out.println(<span class="string">&quot;recordLog&quot;</span>);   </span><br><span class="line">         &#125;   </span><br><span class="line">         <span class="keyword">return</span> rev;   </span><br><span class="line">     &#125;   </span><br><span class="line"> &#125;  </span><br></pre></td></tr></table></figure><p><strong>Spring</strong> 默认采取 <strong>JDK 动态代理</strong> 机制实现 <strong>AOP</strong>，当动态代理不可用时（代理类无接口）会使用 <strong>CGlib</strong> 机制，缺点是：</p><ol><li><p>只能对方法进行切入，不能对接口、字段、static静态代码块、private私有方法进行切入。</p></li><li><p>同类中的互相调用方法将不会使用代理类。因为要使用代理类必须从Spring容器中获取Bean。同类中的互相调用方法是通过 <strong>this</strong> 关键字来调用，<strong>spring</strong> 基本无法去修改 <strong>jvm</strong> 里面的逻辑。</p></li><li><p>使用 <strong>CGlib</strong> 无法对 final 类进行代理，因为无法生成子类了。</p></li></ol><h3 id="4、自定义类加载器"><a href="#4、自定义类加载器" class="headerlink" title="4、自定义类加载器"></a>4、自定义类加载器</h3><p>发生在 <strong>运行期</strong>，于 <strong>字节码加载前</strong>，在类加载到JVM之前直接修改某些类的 <strong>方法</strong>，并将 <strong>切入逻辑</strong> 织入到这个方法里，然后将修改后的字节码文件交给虚拟机运行。</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-007.png" alt="02-spring-core-007"></p><p>典型的代表就是 <strong>javasist</strong>，它可以获得指定方法名的方法、执行前后插入代码逻辑。 </p><p>Javassist是一个编辑字节码的框架，可以让你很简单地操作字节码。它可以在运行期定义或修改Class。使用Javassist实现AOP的原理是在字节码加载前直接修改需要切入的方法。这比使用Cglib实现AOP更加高效，并且没太多限制，实现原理如下图：</p><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-016.jpg" alt="02-spring-core-016"></p><p>我们使用系统类加载器启动我们自定义的类加载器，在这个类加载器里加一个类加载监听器，监听器发现目标类被加载时就织入切入逻辑，我们再看看使用Javassist 实现 AOP 的代码：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/***启动自定义的类加载器****/</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//获取存放CtClass的容器ClassPool   </span></span><br><span class="line"><span class="type">ClassPool</span> <span class="variable">cp</span> <span class="operator">=</span> ClassPool.getDefault();   </span><br><span class="line"><span class="comment">//创建一个类加载器   </span></span><br><span class="line"><span class="type">Loader</span> <span class="variable">cl</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Loader</span>();   </span><br><span class="line"><span class="comment">//增加一个转换器   </span></span><br><span class="line">cl.addTranslator(cp, <span class="keyword">new</span> <span class="title class_">MyTranslator</span>());   </span><br><span class="line"><span class="comment">//启动MyTranslator的main函数   </span></span><br><span class="line">cl.run(<span class="string">&quot;jsvassist.JavassistAopDemo$MyTranslator&quot;</span>, args);  </span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类加载监听器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">class</span> <span class="title class_">MyTranslator</span> <span class="keyword">implements</span> <span class="title class_">Translator</span> &#123;   </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">start</span><span class="params">(ClassPool pool)</span> <span class="keyword">throws</span> </span><br><span class="line">NotFoundException, CannotCompileException &#123;   </span><br><span class="line">&#125;     </span><br><span class="line">  </span><br><span class="line">    <span class="comment">/**  </span></span><br><span class="line"><span class="comment">     * 类装载到JVM前进行代码织入  </span></span><br><span class="line"><span class="comment">     */</span>  </span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">onLoad</span><span class="params">(ClassPool pool, String classname)</span> &#123;   </span><br><span class="line"><span class="keyword">if</span> (!<span class="string">&quot;model$Business&quot;</span>.equals(classname)) &#123;   </span><br><span class="line"><span class="keyword">return</span>;   </span><br><span class="line">&#125;   </span><br><span class="line"><span class="comment">//通过获取类文件   </span></span><br><span class="line"><span class="keyword">try</span> &#123;   </span><br><span class="line"><span class="type">CtClass</span>  <span class="variable">cc</span> <span class="operator">=</span> pool.get(classname);   </span><br><span class="line"><span class="comment">//获得指定方法名的方法   </span></span><br><span class="line"><span class="type">CtMethod</span> <span class="variable">m</span> <span class="operator">=</span> cc.getDeclaredMethod(<span class="string">&quot;doSomeThing&quot;</span>);   </span><br><span class="line"><span class="comment">//在方法执行前插入代码   </span></span><br><span class="line">m.insertBefore(<span class="string">&quot;&#123; System.out.println(\&quot;recordLog\&quot;); &#125;&quot;</span>);   </span><br><span class="line">&#125; <span class="keyword">catch</span> (NotFoundException e) &#123;   </span><br><span class="line">&#125; <span class="keyword">catch</span> (CannotCompileException e) &#123;   </span><br><span class="line">&#125;   </span><br><span class="line">&#125;   </span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;   </span><br><span class="line"><span class="type">Business</span> <span class="variable">b</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Business</span>();   </span><br><span class="line">b.doSomeThing2();   </span><br><span class="line">b.doSomeThing();   </span><br><span class="line">&#125;   </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><p><strong>CtClass</strong> 是一个class文件的抽象描述。也可以使用 <strong>insertAfter()</strong> 在方法的末尾插入代码，或者使用 <strong>insertAt()</strong> 在指定行插入代码。</p><p>使用自定义的类加载器实现AOP在性能上要优于动态代理和Cglib，因为它不会产生新类，但是它仍然存在一个问题，就是如果其他的类加载器来加载类的话，这些类将不会被拦截。</p><h3 id="5、字节码转换"><a href="#5、字节码转换" class="headerlink" title="5、字节码转换"></a>5、字节码转换</h3><p>自定义的类加载器实现AOP只能拦截自己加载的字节码，那么有没有一种方式能够监控所有类加载器加载字节码呢？有，使用Instrumentation，它是 <strong>Java 5</strong> 提供的新特性，使用 <strong>Instrumentation</strong>，开发者可以构建一个字节码转换器，在字节码加载前进行转换。</p><p>发生在 <strong>运行期</strong> ，于 <strong>字节码加载前</strong>，<strong>Java 1.5</strong> 开始提供的 <strong>Instrumentation API</strong> 。<strong>Instrumentation API</strong> 就像是 <strong>JVM</strong> 预先放置的后门，它可以拦截在JVM上运行的程序，修改字节码。</p><p>这种方式是 Java API 天然提供的，在 <strong>java.lang.instrumentation</strong> ，就算 <strong>javasist</strong> 也是基于此实现。</p><p>一个代理实现 <strong>ClassFileTransformer</strong> 接口用于改变运行时的字节码（<strong>class File</strong>），这个改变发生在 <strong>jvm</strong> 加载这个类之前，对所有的类加载器有效。<strong>class File</strong> 这个术语定义于虚拟机规范<strong>3.1</strong>，指的是字节码的 <strong>byte</strong> 数组，而不是文件系统中的 <strong>class</strong> 文件。接口中只有一个方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**  </span></span><br><span class="line"><span class="comment">     * 字节码加载到虚拟机前会进入这个方法  </span></span><br><span class="line"><span class="comment">     */</span>   </span><br><span class="line">    <span class="meta">@Override</span>   </span><br><span class="line">    <span class="keyword">public</span> <span class="type">byte</span>[] transform(  </span><br><span class="line">        ClassLoader         loader,</span><br><span class="line">                String              className,</span><br><span class="line">                Class&lt;?&gt;            classBeingRedefined,</span><br><span class="line">                ProtectionDomain    protectionDomain,</span><br><span class="line">                <span class="type">byte</span>[]              classfileBuffer)</span><br><span class="line">        <span class="keyword">throws</span> IllegalClassFormatException;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 把 classBeingRedefined 重定义之后再交还回去</span></span><br></pre></td></tr></table></figure><p><strong>ClassFileTransformer</strong> 需要添加到 <strong>Instrumentation</strong> 实例中才能生效。</p><h3 id="安全点注意"><a href="#安全点注意" class="headerlink" title="安全点注意"></a>安全点注意</h3><p>当对 JVM 中的字节码进行修改的时候，虚拟机也会通知所有线程通过安全点的方式停下来，因为修改会影响到类结构。</p><h2 id="启动流程"><a href="#启动流程" class="headerlink" title="启动流程"></a>启动流程</h2><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-014.png" alt="02-spring-core-014"></p><p>Bean生命周期管理，基本从无到有（IoC），从有到增强（AOP）。</p><p>任何Bean在Spring容器中只有三种形态，<strong>定义</strong>、<strong>实例</strong>、<strong>增强</strong>。 </p><p>从Bean定义信息观察，通过 <strong>xml</strong> 定义 <strong>bean关系</strong>，<strong>properties</strong>、<strong>yaml</strong>、<strong>json</strong>定义 <strong>属性</strong>，bean关系和属性就构成Bean的定义，其中<strong>BeanDefinitionReader</strong>负责扫描定义信息生成Bean定义对象 <strong>BeanDefinition</strong>。在此基础上，允许对 <strong>BeanDefinition</strong> 定义进行增强（Mybatis与Spring存在很多使用场景）。  </p><p>Bean定义完成之后，开始通过反射实例化对象、填充属性等，同时又再次预留了很多增强的口，最终生成一个完整的对象。</p><h3 id="实例化流程与三级缓存"><a href="#实例化流程与三级缓存" class="headerlink" title="实例化流程与三级缓存"></a>实例化流程与三级缓存</h3><p>从定义到扩展，然后反射实例化，到增强，每种状态都会存在引用。</p><p>所以Spring设计 <strong>三级缓存</strong>，说白了是对应存储Bean生命周期的三种形态:</p><ul><li>定义</li><li>实例</li><li>增强</li></ul><p><img src="https://www.panshenlian.com/images/post/java/spring/spring-core/02-spring-core-015.png" alt="02-spring-core-015"> </p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>Spring 就是 <strong>反射</strong> + <strong>字节码增强</strong>。</p><ul><li><p>反射，为了 IoC 和 <strong>解耦</strong></p></li><li><p>字节码增强，为了 <strong>简化</strong> 和声明式编程</p></li></ul><p>深刻理解 Spring 这两部分核心特性，关于 spring、springboot、springcloud 的所有语法糖设计与使用，就自然清楚。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://dzone.com/articles/java-agent-1">Understanding Java Agents</a></li><li><a href="https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/instrument/package-summary.html">Java 1.5-java.lang.instrument</a></li><li><a href="https://www.jianshu.com/p/26e9f410235f">ASM 字节码插桩</a></li><li><a href="https://github.com/alibaba/arthas">arthas</a></li><li><a href="https://asm.ow2.io/">ASM</a></li><li><a href="https://github.com/cglib/cglib">cglib</a></li><li><a href="http://www.javassist.org/">javassist</a></li><li><a href="https://jse.readthedocs.io/en/latest/jdk8/javassistLog.html">Javassist/ASM Audit Log</a></li><li><a href="https://bytebuddy.net/#/tutorial">bytebuddy tutorial</a></li><li><a href="https://www.jrebel.com/blog/java-code-generation-libraries-comparison">Performance Comparison of cglib, Javassist, JDK Proxy and Byte Buddy</a></li><li><a href="https://baike.baidu.com/item/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC">控制反转</a></li><li><a href="https://www.iteye.com/topic/1116696">AOP 的实现机制</a></li><li><a href="https://www.jianshu.com/p/41632f76dd62">Spring AOP 总结</a></li><li><a href="https://zhuanlan.zhihu.com/p/448871215">javaAgent、ASM、javassist、ByteBuddy 是什么？</a></li></ul><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/java/spring/spring-core/spring-core-cover.jpg" alt="spring-core-cover"&gt;&lt;/p&gt;
&lt;h2 id="框架的</description><enclosure length="0" type="application/octet-stream" url="https://www.jianshu.com/p/26e9f410235f"/><itunes:explicit/><itunes:subtitle>&lt;h2 id="框架的</itunes:subtitle><itunes:summary>&lt;h2 id="框架的</itunes:summary><itunes:keywords>AOP, 一文读懂, Spring, IOC, 反射</itunes:keywords></item><item><title>无聊科技正经事周刊（第4期）：理性囤货与人工智能预测</title><category>科技周刊</category><category>理性</category><category>人工智能</category><pubDate>Tue, 26 Apr 2022 16:05:12 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/04/27/weekly-4/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/cover.jpg" alt="封面图"></p><p>位于曼哈顿的 84 层豪华公寓楼施坦威大厦的高宽比为 24:1，正式成为世界上最薄的摩天大楼，大楼如此 “瘦”，可能会在空中摇摆。(<a href="https://www.odditycentral.com/architecture/worlds-thinnest-skyscraper-is-so-skinny-it-may-sway-in-the-air.html">via</a>)</p><h2 id="本周话题：理性囤货与人工智能预测"><a href="#本周话题：理性囤货与人工智能预测" class="headerlink" title="本周话题：理性囤货与人工智能预测"></a>本周话题：理性囤货与人工智能预测</h2><p>这两天，周围有一些囤货的声音。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/topic-001.jpg" alt="话题"></p><p>而在声音之外，我对恐慌式囤货，有一些猜想：</p><ul><li>其一，是部分人对供应链的不信任</li><li>其二，是不实消息肆无忌惮的传播</li></ul><p>换而言之，要消除非理性囤货的恐慌，我们可能需要解决两个问题：</p><ul><li>第一，保障供应链</li><li>第二，阻断谣言传播</li></ul><p>这样一来，问题好像就变成：</p><ul><li>如何了解供求关系？</li><li>如何识别谣言？</li></ul><p>而最终面对的这两个问题，对于 <strong>人工智能</strong> 来说，好像是有解的。我们先来了解两个研究报告：</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/topic-002.jpg" alt="话题"></p><p>（1）<a href="https://www.psu.edu/news/agricultural-sciences/story/using-tweets-predict-real-time-food-shortages/">《使用推文预测实时粮食短缺》</a></p><p>宾夕法尼亚州大学公园——宾夕法尼亚州立大学和宾夕法尼亚州立大学的研究人员表示，推特上的推文所表达的 <strong>情绪和情感</strong> 可以被实时用来 <strong>评估</strong> 大流行病、战争或自然灾害 <strong>供应链中断</strong> 可能导致的粮食短缺。 </p><p>六个月时间，研究人员通过分析约 120 万条密切相关的推文，使用所谓的上下文语言模型来提取每条推文中的情绪和情感，包括愤怒、厌恶、恐惧、喜悦、悲伤、惊讶、兴奋等。</p><p>他们发现，在 COVID-19 大流行初期，表达愤怒、厌恶和恐惧情绪的推文与官方报告的实际地区粮食不足率密切相关。研究人员表示，这些发现有可能用于开发低成本的早期 <strong>预警系统</strong>，以确定最需要粮食安全干预措施的地方。 </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/topic-003.jpg" alt="话题"></p><p>（2）<a href="https://ink.library.smu.edu.sg/cgi/viewcontent.cgi?article=5633&context=sis_research">《使用递归神经网络从微博中检测谣言》</a> 与 <a href="https://ojs.aaai.org/index.php/AAAI/article/view/5393">《使用双向图卷积网络在社交媒体上进行谣言检测》</a></p><p>社交媒体是散布谣言的理想场所，因其传播新信息的性质而在公共领域迅速发展，导致谣言四处流传，<strong>自动辟谣</strong> 是一个关键问题，从社交媒体的海量信息中发现谣言正成为一项艰巨的挑战。</p><p>因此，一些深度学习方法被用于检测谣言，两篇论文中使用了两种不同的检测模型，例如 <strong>递归神经网络（RvNN）</strong> 、 <strong>双向图卷积网络（Bi-GCN）</strong> ，根据社交平台的数据集和谣言特征，从谣言的传播方式和传播模式等方面，快速、准确地检测谣言。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/topic-004.jpg" alt="话题"></p><p>（<a href="https://zhuanlan.zhihu.com/p/342413803">via</a>）</p><p>总之，人工智能可以在很多方面发挥作用，有助于早期预警干预供应链，一定程度上缓解囤货恐慌，但问题的发展往往是复杂的。因此，我们应该结合实际，科学分析。</p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="http://mp.weixin.qq.com/s?__biz=MjM5OTYyNzAyMA==&mid=2650210031&idx=2&sn=888465c3ef2ce87b64c9af92afdf0c1e&chksm=bf3ace8c884d479a1564604309753fddd86346cf823f5400aa9969983d8ae2e3ede81da97100&scene=27#wechat_redirect">马斯克440亿美元收购推特</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-017.jpg" alt="马斯克"></p><p>(<a href="https://www.tmz.com/2022/04/25/elon-musk-new-owner-twitter-43-billion-offer-accepted/?adid=social-twa">via</a>)</p><p>当地时间 4 月 25 日，社交媒体平台推特公司接受了埃隆·马斯克的收购协议。根据协议，马斯克将以每股 54.2 美元，总计约 440 亿美元的价格收购推特。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-016.jpg" alt="马斯克"></p><p>多年来，Twitter 一直在努力实现有意义的业务增长，但其股价几乎与 2013 年持平，该公司最近的价值约 350 亿美元，而马斯克总约 2600 亿美元。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-015.jpg" alt="马斯克"></p><p>(<a href="https://twitter.com/Saagarshinde8/status/1518823733171003393/photo/1">via</a>)</p><p>马斯克表示他希望将 Twitter 私有化，他认为这将使该平台能够通过替代方案和 <strong>开源算法</strong> 更好地服务于言论的 “社会需求” 。</p><p>2、<a href="https://www.reuters.com/lifestyle/science/japan-researchers-develop-electric-chopsticks-enhance-salty-taste-2022-04-19/">日本研究人员研制出电动筷子以增强咸味</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-002.jpg" alt="日本研究人员研制出电动筷子以增强咸味"></p><p>日本研究人员研发出可增强咸味的电脑筷子，可能有助于那些需要减少饮食中钠含量的人。</p><p>这款筷子由明治大学教授 Homei Miyashita 和饮料制造商 Kirin Holdings Co. (2503.T) 共同研发，通过电刺激和佩戴在腕带上的迷你电脑来提升口味。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-001.jpg" alt="日本研究人员研制出电动筷子以增强咸味"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-003.jpg" alt="日本研究人员研制出电动筷子以增强咸味"></p><p>Miyashita 说，该设备使用 <strong>微弱的电流</strong> 将食物中的 <strong>钠离子</strong> 通过筷子传输到嘴巴，在那里它们会产生咸味。</p><p>3、<a href="https://www.theverge.com/2022/4/22/23037654/tesla-crash-private-jet-reddit-video-smart-summon">一辆特斯拉在智能召唤模式下撞上价值高达 350 万美元的私人飞机</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-004.jpg" alt="特斯拉"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-005.jpg" alt="特斯拉"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-005-1.jpg" alt="特斯拉"></p><p>上周五，一辆特斯拉似乎在其自动驾驶智能召唤模式下撞上了一架价值 300 万至 350 万美元的私人飞机（<strong>尾翼</strong>），致使飞机原地旋转了 90 度。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-006.jpg" alt="特斯拉"></p><p>(<a href="https://www.tesla.com/modely">Model Y</a>)</p><p>Smart Summon（<a href="https://www.tesla.com/ownersmanual/modely/is_is/GUID-6B9A1AEA-579C-400E-A7A6-E4916BCD5DED.html">智能召唤</a>）是通过使用手机 GPS 作为目标目的地，用户可以定位或选择指定位置，让 Model Y 根据需要进行行驶和停车，这确实在需要移出狭窄的停车位、穿过水坑或其他简单移动的场景下很有帮助。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-007.jpg" alt="特斯拉"></p><p>当然，特斯拉也列举了很多注意项和警告项，并且强调 “智能召唤” 是 BETA feature （测试版功能），在使用期间需要自己密切监视车辆和周围环境。</p><p>总之，购买 Model Y 的伙伴，值得一试。</p><p>4、<a href="https://www.infoworld.com/article/3653331/jdk-19-the-features-targeted-for-java-19.html">JDK 19 将于 9 月发布</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-008.jpg" alt="JDK19"></p><p>Java Development Kit 19 将于今年 9 月发布，现在正式提出了第二个针对它的特性：用于表达向量计算的 Vector API ，它将在下一个 Java 版本中进行第四次孵化。另一个提议的功能是将 JDK 移植到开源 Linux/RISC-V 指令集架构 (ISA)。</p><p>最近关于开源 Linux/RISC-V 指令集架构 (ISA) 的热度不低，值得持续关注。</p><p>5、<a href="https://www.theverge.com/2022/4/23/23038870/apple-app-store-widely-remove-outdated-apps-developers">“过时”的 iphone 应用即将被 App Store 踢出/下架</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-009.jpg" alt="iphone"></p><p>最近，部分开发者收到标题为 “应用程序改进通知” 的邮箱：</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-012.jpg" alt="iphone"></p><p>苹果警告称，它将从 App Store 中删除 “在很长一段时间内没有更新” 的应用程序，并给开发者 30 天的时间来更新它们。</p><p>但是，该强制通知引起一些争论。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-010.jpg" alt="iphone"></p><p>▲ 许多应用程序制造商，如 Protopop Games 开发商 Robert Kabwe，表达了他们对这一变化的担忧，Kabwe在 Twitter 上表示，苹果威胁要删除他的全功能游戏 Motivoto，因为它自 2019 年 3 月以来一直没有更新。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-011.jpg" alt="iphone"></p><p>▲ 与此同时，FlickType Apple Watch 键盘的开发者 Kosta Eleftheriou 表示，由于两年没有更新，Apple 取消了他专为视障人士设计的应用程序版本。正如 Eleftheriou 在他的推文中指出的那样，曾经超级流行的 Pocket God 应用程序仍然保留在 App Store 中，尽管它在 2015 年获得了最后一次更新。开发人员 Emilia Lazer-Walker 还报告称苹果正在删除她的 “一些” 来自 App Store 的旧游戏。其他几位开发人员也有同样的经历，并指出他们只是没有时间更新他们的应用程序。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-013.jpg" alt="iphone"></p><p>▲ 我发现在苹果官方的 <a href="https://developer.apple.com/support/app-store-improvements/">App Store 改进页面上</a>，苹果公司表示：“我们正在实施评估应用程序的持续流程，删除不再按预期运行、不遵循当前审查指南或已过时的应用程序。” 页面上由于没有发布日期，因此无法清楚苹果是何时发布的，或者上次更新该帖子是什么时间。</p><p>但无论如何，苹果的这个强制通知，我个人觉得有利有弊，对于苹果市场来说是一个淘汰、激活和升级推动，但对于部分开发者来说，可能会直接断送应用，毕竟有些应用根本就没有规划新功能。</p><p>6、<a href="https://www.edinburghnews.scotsman.com/lifestyle/travel/uk-first-as-driverless-bus-begins-testing-on-edinburgh-park-tram-and-train-interchange-route-3666306">英国率先推出“无人驾驶”巴士，开始在爱丁堡公园电车和火车换乘路线上进行测试</a> </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/keji-014.jpg" alt="无人驾驶"></p><p>▲ 苏格兰最新的公共汽车将自动驾驶，成为英国第一辆上路的全尺寸自动驾驶汽车。</p><p>巴士集团 Stagecoach 将从周一开始对其自动驾驶巴士进行道路测试，为今年夏天晚些时候上车的乘客做好准备。该公司在苏格兰的区域总监 Sam Greet 认为：“ 这是一个非常令人兴奋的项目，是我们在全面推出英国首个全尺寸自动驾驶巴士服务的过程中向前迈出的重要一步，并将提供前往苏格兰中心全新巴士路线的便捷通道。”</p><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://ai.googleblog.com/2022/03/auto-generated-summaries-in-google-docs.html">Google Docs 自动生成摘要</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/linggan-001.jpg" alt="Google Docs 自动生成摘要"></p><p><a href="https://ai.googleblog.com/">Google AI</a> 最近宣布，Google Docs 现在会自动生成建议，在文档编写者有需要的时候帮助他们创建内容摘要。</p><p>简单来说，就像我们以前学语文的时候，老师让我们总结中心思想。</p><p>我个人觉得这个 AI 技能有点恐怖，这个可不像机器翻译只是简单的根据文本信息然后通过 AI 模型做自然语言处理，而是要通读全文，理解读者场景语义特征等等（我猜的），最终提炼出全文的中心思想创建成一段核心概要，这过程极其复杂，但是人工智能和大数据，毕竟在 Google 这里一直就很前沿，所以正常。</p><p>我试了很久，没有试出来这个功能，而且我怀疑目前仅仅支持英文，毕竟不同语言差异性很大。当然，如果有小伙伴试出来了，欢迎分享一下过程。 </p><p>2、<a href="https://www.odditycentral.com/travel/unique-tokyo-cafe-only-serves-struggling-writers-working-on-tight-deadlines.html">东京一家特别的 “写稿咖啡馆”</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/linggan-002.jpg" alt="一家只为赶稿作家提供服务的东京咖啡馆"> </p><p>店主河合拓哉发推文：“手稿写作咖啡馆，只允许有写作期限的人光临！这是为了在咖啡馆保持一定程度的专注和紧张的气氛！谢谢你的理解。”</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/linggan-003.jpg" alt="一家只为赶稿作家提供服务的东京咖啡馆"> </p><p>这家咖啡馆仅在几天前开业，按使用时间向顾客收费（每 30 分钟收费 150 日元或 1.32 美元），并配备 USB 端口、电脑支架和免费 Wi-Fi。苦苦挣扎的作家也可以自己带食物和饮料，或者因为咖啡和水是唯一可用的东西，所以将它们送到那里，但在实际写作方面非常严格。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/linggan-004.jpg" alt="一家只为赶稿作家提供服务的东京咖啡馆"> </p><p>Manuscript Writing Cafe （手稿写作咖啡馆）所在的空间实际上是一个名为 Koenji Sankakuchitai的录音和广播工作室。咖啡馆本身只有在工作室不营业的时候才开放，所以你不可能每天都去。然而，河合拓哉保证会经常宣布咖啡厅的下一个营业日期。比如本月，手稿写作咖啡厅将在下一个20号开放。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/linggan-005.jpg" alt="一家只为赶稿作家提供服务的东京咖啡馆"> </p><p>日本对有时会激发全球潮流的另类咖啡馆并不陌生。还记得猫（主题）咖啡馆吗？这种流行趋势起源于亚洲国家，女仆咖啡馆、猫头鹰咖啡馆、爬行动物咖啡馆，甚至还有专门针对女性大腿的咖啡馆。这些只是少数几个例子，实际上，日本已经提出了许多有趣的咖啡馆概念，并且不知何故不断提出新的概念，本文就是最新的例子：东京高圆寺社区的手稿写作咖啡馆，特点是只欢迎努力赶稿的作家。 </p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://www.heinrichhartmann.com/posts/writing">关于工程师写作这件事</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/article-002.jpg" alt="关于工程师写作这件事"></p><p>(<a href="https://zhuanlan.zhihu.com/p/85484159">via</a>)</p><p>写作是高级软件工程师职业发展所需的一项重要技能。本文包含有关如何成为更好、更有生产力的作家的技巧。它涵盖了写作前要考虑的事情，如何写好，以及如何练习写作。</p><p>写作很难，但在大型组织中可能会是产生影响的关键。作为一名高级软件工程师，写作是你必须掌握的最重要的技能，以扩大你在团队之外的影响力，并推进你的职业生涯。</p><p>2、<a href="https://stackoverflow.com/questions/363681/how-do-i-generate-random-integers-within-a-specific-range-in-java">如何在 Java 中生成特定范围内的随机整数？</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/article-001.jpg" alt="生成特定范围内的随机整数"></p><p>这是一个 stackoverflow 上一个高赞问答，提问者想知道如何在 Java 中生成特定范围内的 int 类型随机值，他尝试了几种方法，但都出现错误。</p><p><strong>尝试1：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">randomNum = minimum + (<span class="type">int</span>)(Math.random() * maximum);</span><br></pre></td></tr></table></figure><p>错误：随机结果会出现 <strong>大于</strong> maximum 的情况！</p><p><strong>尝试2：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">Random</span> <span class="variable">rn</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line"><span class="type">int</span> <span class="variable">n</span> <span class="operator">=</span> maximum - minimum + <span class="number">1</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> rn.nextInt() % n;</span><br><span class="line">randomNum =  minimum + i;</span><br></pre></td></tr></table></figure><p>错误：随机结果会出现 <strong>小于</strong> minimum 的情况！</p><p><strong>最后，</strong> 有位高赞网友解释，在  Java 1.7 或更高版本中，使用 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/ThreadLocalRandom.html#nextInt(int,int)">ThreadLocalRandom.current().nextInt</a> : </p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.concurrent.ThreadLocalRandom;</span><br><span class="line"><span class="type">int</span> <span class="variable">randomNum</span> <span class="operator">=</span> ThreadLocalRandom.current().nextInt(min, max + <span class="number">1</span>);</span><br></pre></td></tr></table></figure><p>而在 Java 1.7 之前，建议仍使用 <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Random.html#nextInt(int)">java.util.Random.nextInt</a> ，特别是在实践中，java.util.Random 类通常比 java.lang.Math.random() 更可取：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> java.util.Random;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="type">int</span> <span class="title function_">randInt</span><span class="params">(<span class="type">int</span> min, <span class="type">int</span> max)</span> &#123; </span><br><span class="line">    Random rand;</span><br><span class="line">    <span class="type">int</span> <span class="variable">randomNum</span> <span class="operator">=</span> rand.nextInt((max - min) + <span class="number">1</span>) + min;</span><br><span class="line">    <span class="keyword">return</span> randomNum;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当然，这个问答下面还有其他一些网友也提供了很多不同的方案，例如有位网友提出实现一种标准方案，使用了 Java Math 库中的函数 Math.random()：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Min + (<span class="type">int</span>)(Math.random() * ((Max - Min) + <span class="number">1</span>))</span><br></pre></td></tr></table></figure><p>或许你也可以看看，关于各种方案对单精度、双精度的讨论中，是否有一种是你平时使用的方案？你的方案是否也存在精度错误问题？</p><p>3、<a href="https://ai.googleblog.com/2022/04/pix2seq-new-language-interface-for.html">Googel AI 推出一个用于目标检测的语言建模框架——Pix2Seq</a>（英文，附论文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/article-003.gif" alt="Googel"></p><p>最近，Googel AI 研究科学家提出了一种简单通用的方法，可以从完全不同的角度处理目标检测。研究结果发表在 <a href="https://iclr.cc/">ICLR 2022</a> 。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/article-004.jpg" alt="Googel"></p><p>▲ 详见论文 <a href="https://arxiv.org/pdf/2109.10852.pdf">《 Pix2Seq: A Language Modeling Framework for Object Detection 》</a>。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/article-005.jpg" alt="Googel"></p><p>▲ Pix2Seq 可以检测人口密集和复杂场景中的目标对象。相比现有的 <a href="https://arxiv.org/abs/1506.01497">Faster R-CNN</a> 和 <a href="https://alcinos.github.io/detr_page/">DETR</a> ，Pix2Seq 在大规模目标检测 <a href="https://cocodataset.org/#home">COCO</a> 数据集上取得了有竞争力的结果，并且可以通过在更大的目标检测数据集上预训练模型来进一步提高其性能。</p><p>4、<a href="https://www.freebuf.com/news/330953.html">Java加密漏洞PoC代码公开，受影响的版本需尽快升级</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/article-006.jpg" alt="Googel"></p><p>Security affairs 网站消息，4月21日，安全研究人员 Khaled Nassar 在 Github 上公开了 Java 中新披露的数字签名绕过漏洞的 PoC 码，该漏洞被追踪为 CVE-2022-21449（CVSS 分数：7.5）。</p><p>漏洞的影响范围主要涉及 Java SE 和 Oracle GraalVM 企业版的以下版本 ：</p><ul><li>Oracle Java SE：7u331、8u321、11.0.14、17.0.2、18</li><li>Oracle GraalVM 企业版：20.3.5、21.3.1、22.0.0.2</li></ul><p>目前，甲骨文已在 4 月 19 日最新发布的 4 月补丁中修复了该漏洞，但由于 PoC 代码的公布，建议在其环境中使用 Java 15、Java 16、Java 17 或  Java 18 的系统组织尽快修复。</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://github.com/sirwart/secrets">secrets：防止将密码提交到源代码中的命令行工具</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-001.jpg" alt="secrets"><br>(<a href="https://www.sohu.com/a/280490064_100051639">via</a>)</p><p>secrets 是 github 上一款开源的命令行工具，用于防止将密码提交到您的源代码中，secrets 有一些区别于其他密码扫描工具的特点：</p><ul><li><p><strong>专注预提交</strong><br>基本目前大部分扫描代码的工具都是在预提交阶段检查的，属于常见做法。</p></li><li><p><strong>极快</strong><br>github 上声称比其他工具快 95 倍或更多，扫描速度通常是我们提交代码时很关心的一个问题。</p></li><li><p><strong>本地执行</strong><br>这个是安全性的保障，有些扫描工具可能会将源代码自动发送到第三方服务做检查。</p></li><li><p><strong>误报率低</strong><br>github 上声称使用了基于概率论的方法，实际会比其他工具更准确地检测密钥。</p></li><li><p><strong>无依赖</strong><br>这点很关键，在安装和升级方面很便利。</p></li></ul><p>我认为代码检查很有必要，无论是在代码质量检查方面，还是在防止密码泄露方面，因此推荐使用其一工具，如果您觉得 secrets 不适合，这里还提供了几款 Github 上的替代工具：</p><ul><li><a href="https://github.com/Yelp/detect-secrets">detect-secrets</a> , Star 2.3k</li><li><a href="https://github.com/trufflesecurity/trufflehog">trufflehog</a>, Star 8.1k</li><li><a href="https://github.com/zricethezav/gitleaks">gitleaks</a>，Star 9.6k</li></ul><p>2、<a href="https://skyline.github.com/">skyline：生成 GitHub 贡献图的 3D 模型</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-002.gif" alt="skyline"></p><p>查看 GitHub 贡献图的 3D 模型，支持分享、打印等等。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-003.jpg" alt="senlypan"></p><p>目前只支持到 2021 年，我简单试了一下<a href="https://github.com/senlypan">自己的 Github(@senlypan) 仓库</a>，好家伙，2021 年基本在划水… 零星几栋楼。</p><p>3、<a href="http://spacetime.how/">SpaceTime：轻量级时区库</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-004.jpg" alt="SpaceTime"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-005.jpg" alt="SpaceTime"></p><p>SpaceTime：轻量级时区库，差不多 40 KB。使用它来计算其他时区的时间。具有类似 Moment 的 API，但它是不可变的，并且没有依赖关系。</p><p>4、<a href="https://handsontable.com/blog/articles/2022/4/whats-new-in-hyperformula-2.0.0">HyperFormula 2.0.0：类似电子表格的计算引擎</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-006.jpg" alt="HyperFormula"></p><p>HyperFormula 是一个强大的 Excel 启发的电子表格系统（不仅仅是一个控件）。请注意，它是开源的，但具有双重许可。</p><p>5、<a href="https://picmojs.com/">PicMo：一个简单的 JavaScript 表情符号选择器</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-007.jpg" alt="HyperFormula"></p><p>PicMo：一个简单的 JavaScript 表情符号选择器。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-008.jpg" alt="HyperFormula"></p><p>让您可以选择使用平台原生表情符号或通过 Twemoji 跨平台的表情符号，您甚至可以添加自己的自定义表情符号。</p><p>6、<a href="https://uiball.com/loaders/">Loaders：一款美化网页加载效果的工具</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-009.jpg" alt="HyperFormula"></p><p>Loaders 是一款美化网页加载效果的工具，使用简单。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-010.jpg" alt="Loaders"></p><p>官网上包含一系列免费的加载效果和调试指导，这些组件是用 HTML、CSS 和 SVG 构建的，相关组件代码可以很容易地从官网上复制使用。</p><p>7、<a href="https://github.com/esimov/triangle">Triangle 2.0：一款可以把图像转换为三角形艺术效果的工具</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/tools-011.jpg" alt="Triangle"></p><p>Triangle 2.0 ，使用 <a href="https://baike.baidu.com/item/Delaunay%E4%B8%89%E8%A7%92%E5%89%96%E5%88%86%E7%AE%97%E6%B3%95/3779918">Delaunay 三角剖分算法</a> 将图像转换为三角形艺术效果。</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://hackernoon.com/glossary-for-non-technies">非技术人员词汇表</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-001.jpg" alt="非技术人员词汇表"></p><p>大部分非技术人员，对 “服务器”、“调试” 、“开源” 、“编译” 、“前端” 、“库” 、“Docker” 、“MySQL” 等名词不太了解，这篇文章收集了很多简而易懂的技术名词解释。</p><p>2、<a href="https://iotdb.apache.org/">Apache IoTDB（物联网数据库）</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-003.jpg" alt="IoTDB"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-004.jpg" alt="IoTDB"></p><p>Apache IoTDB（物联网数据库）是一个物联网原生数据库，具有高性能的数据管理和分析功能，可部署在边缘和云端。Apache IoTDB 由于其轻量级架构、高性能和丰富的特性集以及与 Apache Hadoop、Spark 和 Flink 的深度集成，可以满足物联网中海量数据存储、高速数据摄取和复杂数据分析的需求工业领域。</p><p><a href="https://github.com/apache/iotdb">Github 仓库</a> : <a href="https://github.com/apache/iotdb">https://github.com/apache/iotdb</a></p><p>3、<a href="https://stackdiary.com/node-js-frameworks/">2022 年最受欢迎的 Node.js 框架</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-005.jpg" alt="Node.js"></p><p>经过多年的稳定统治，Node.js 仍然是最受欢迎的 JavaScript 运行环境。但在最近的历史中，Node.js 框架的格局发生了巨大变化。越来越多的框架被构建为混合（元）解决方案，不仅迎合后端，也迎合全栈开发人员。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-006.jpg" alt="Node.js"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-007.jpg" alt="Node.js"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-008.jpg" alt="Node.js"></p><p>文章中探讨了当前趋势，并探索最流行的 Node.js 框架，包括但不限于：</p><ul><li>Next.js</li><li>Nest</li><li>Strapi</li><li>Remix</li><li>Nuxt</li><li>SvelteKit</li><li>Fastify</li><li>Redwood</li><li>Express</li><li>Adonis</li><li>Keystone</li></ul><p>你是否在使用以上的 Node.js 框架，体验如何？</p><p>4、<a href="https://www.gutenberg.org/">古腾堡计划——超过 6 万本免费电子书的图书馆</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-009.jpg" alt="古腾堡计划"></p><p>古腾堡计划是一个拥有 60,000 多本免费电子书的图书馆，用户可以选择免费的 epub 和 Kindle 电子书，进行下载或在线阅读，特别是可以找到世界上最伟大的 <strong>文学作品</strong>，古腾堡计划重点关注美国版权已过期的旧作品，成千上万的志愿者将电子书数字化并认真校对，供您欣赏。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-010.jpg" alt="古腾堡计划"></p><p>网站提供了一些热门电子书的下载排名情况，如果碰巧你也喜欢阅读文学电子书，这个网站绝对适合你。</p><p>5、<a href="https://engineering.linkedin.com/blog/2022/open-sourcing-feathr---linkedin-s-feature-store-for-productive-m">LinkedIn开源了一个企业级的高性能功能商店（机器学习）</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/source-011.jpg" alt="Feathr"></p><p>最近 LinkedIn 宣布了一个新的开源项目，以简化机器学习 (ML) 功能管理和提高开发人员生产力。</p><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://stratechery.com/2022/dall-e-the-metaverse-and-zero-marginal-content/">DALL-E、元宇宙和零边际内容</a>（英文）</p><p>最近， <a href="https://www.theverge.com/2022/4/6/23012123/openai-clip-dalle-2-ai-text-to-image-generator-testing">OpenAI 发布了 DALL-E 2</a>，Dall-E 2 是来自 OpenAI 的新 AI 系统，DALL-E 是通过在图像及其文本描述上训练神经网络而创建的，通过深度学习，它不仅可以理解像考拉熊和摩托车这样的单个物体，还可以从物体之间的关系中学习，当你向 DALL-E 索要 “考拉骑摩托车” 的图像时，它知道如何创建那个或任何东西与另一个对象或动作的关系。</p><p>简而言之，Dall-E 2 能根据文本提示生成（或编辑）图像，来自 <a href="https://twitter.com/BecomingCritter/status/1511808277490896903">@BecomingCritter</a> 的这条推特帖子发布了很多有意思的 AI 图示。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-001.jpg" alt="DALL-E、元宇宙和零边际内容"></p><p>▲ 泰迪熊在 1980 年代在月球上进行新的人工智能研究。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-002.jpg" alt="DALL-E、元宇宙和零边际内容"></p><p>▲ 一个沐浴在 AGI 乌托邦阳光下的人类。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-003.jpg" alt="DALL-E、元宇宙和零边际内容"></p><p>▲ 戴贝雷帽和黑色高领毛衣的柴犬狗。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-004.jpg" alt="DALL-E、元宇宙和零边际内容"></p><p>▲ 一只在喜马拉雅山冥想寻求启蒙的聪明猫。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-005.jpg" alt="DALL-E、元宇宙和零边际内容"></p><p>▲ 火星上的一座城市。 </p><p>关于 DALL-E 和 GPT 以及类似的机器学习模型，未来我们可能有几个值得思考的点：</p><ul><li>极大补充丰满了元宇宙的未来，推动了社交、游戏、视频以及媒体等内容快速进化</li><li>互联网生产成本几乎为零，让消费的边际成本降到了零</li></ul><p>延伸推荐阅读：<a href="https://stratechery.com/2019/the-internet-and-the-third-estate/">《互联网与第三产业》</a></p><p>2、<a href="https://twitter.com/stem_feed/status/1518206411481575424">1982年日本精工电视手表，当时的技术非常惊人</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-006.jpg" alt="seiko"></p><p>▲ 最近推上有个热门的帖子，网友 @stem_feed 发了一个推 “The Seiko TV watch, 1982. Pretty amazing tech for its days.”</p><p>内容是 Seiko（日本精工）在 40 年前推出的一款带有显示屏、可交互娱乐的手表，也就是 SEIKO 精工在 1982 年发明的 “TV WATCH” 电视手表，可想而知当时的技术非常惊人。</p><p>Seiko TV Watch 的手表部分乍一看和普通的不锈钢电子表没有太大的区别，不同的是，它除了手表还附带了一堆其他装备——这是世界上第一个有调谐钮、耳机插孔，随时随地可以看节目的手表。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-007.jpg" alt="seiko"></p><p>表身有上下两个独立屏幕：上屏幕是普通电子表的屏幕，可以看时间、设置闹铃、日历、秒表等；下屏幕尺寸是 16.8×25.2mm，是个 31920 像素（210×152）的液晶屏，用于视频输出，配备 VHF&amp;UHF 调谐器，同时可收听 FM 广播。看电视的话，电池续航能力为 5 小时。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-008.jpg" alt="seiko"></p><p>这在当时绝对是款划时代的产品，当时的 SEIKO 精工光光研发就用了三年的时间，投入数亿日元。</p><p>当然，这款表还在 1983 年登陆美国，在 007 系列电影等电影中带过货，以下列举部分场景。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-013.jpg" alt="seiko"></p><p>▲ 在 1983 年罗杰·摩尔主演的电影《007 之八爪女》</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-009.jpg" alt="seiko"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-010.jpg" alt="seiko"></p><p>▲ 在 1984 年的《捉鬼敢死队》</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-011.jpg" alt="seiko"></p><p>▲ 在 1986 年电影《怒月冲天》中出现的 UC-2000</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/photo-012.jpg" alt="seiko"></p><p>▲ 在 1987 年汤姆·汉克斯主演的电影《天罗地网》</p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/318975914">自然语言处理的前世、今生以及广泛应用</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/boke-001.jpg" alt="自然语言处理的前世、今生以及广泛应用"></p><p>(<a href="https://voi.id/zh/technology-zh/81256/read">via</a>)</p><p>自然语言处理是让计算机和人类用“人话”交流的领域。如其他人工智能领域一样，自然语言处理也经历了早期的规则、信号处理、统计机器学习和深度学习时代。自然语言处理在近些年取得长足的突破之后，打开了广泛的应用场景，也面临着一些挑战。本期节目特别介绍了很多的自然语言处理的应用，让大家大开眼界。</p><p><strong>嘉宾</strong>：崔安颀（<a href="https://www.rsvp.ai/cn">薄言RSVP.ai</a> 联合创始人、AI负责人，《大数据智能：数据驱动的自然语言处理技术》一书编著者）</p><p><strong>主持</strong>：Sean Wang、斯图亚特</p><p><strong>剪辑</strong>：斯图亚特</p><p>本期内容包括：</p><ul><li>什么是自然语言处理、自然语言的边界</li><li>自然语言处理的历史：起源、基于规则的翻译、信号处理、统计机器学习、深度学习</li><li>BERT为什么这么灵？</li><li>机器阅读理解</li><li>自然语言处理遇到的挑战</li><li>自然语言处理的多种应用</li><li>计算机真的懂了吗?</li><li>语义的理解</li><li>推荐图书 </li></ul><p><strong>推荐读物</strong>：  </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/boke-002.jpg" alt="推荐读物"></p><p>▲  <a href="https://book.douban.com/subject/35033507/">《数学之美》</a>  </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/boke-003.jpg" alt="推荐读物"></p><p>▲  <a href="https://book.douban.com/subject/34907320/">《大数据智能：数据驱动的自然语言处理技术》 </a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/boke-004.jpg" alt="推荐读物"></p><p>▲  <a href="https://book.douban.com/subject/25746399/">《统计自然语言处理》 </a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/boke-005.jpg" alt="推荐读物"></p><p>▲  <a href="https://book.douban.com/subject/35139910/">《Representation Learning for Natural Language Processing》 </a></p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://nilsnh.no/2022/04/09/innovating-beyond-libraries-and-frameworks/">超越库和框架的创新</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/post-001.jpg" alt="超越库和框架的创新"></p><p>Netlife Bergen 的首席技术官 Nils Norman Haukås 提醒我们不要过分沉迷于最新的 <strong>框架</strong> 和 <strong>库</strong>，也许最好专注于 <strong>原则</strong> 和 <strong>模式</strong>。</p><p>文章的主要观点是：</p><ul><li><p>框架与库很显然简化了我们的应用开发，并且做了很多抽象和优化，使用者完全不必关注底层实现。但是存在维护性和安全性等问题，例如很多开源框架软件没有及时更新升级，甚至是停止维护了，另外在安全性方面，最近的 log4j 和 spring 框架严重漏洞，也让很多企业和个体陷入危险之中甚至造成损失。</p></li><li><p>但如果我们能专注于 <strong>原则</strong> 和 <strong>模式</strong> 并自我设计，那么会避免遇到维护性和安全性等问题，并且能更好的构建项目以及适应未来的变化。</p></li></ul><p>总之，以我个人的经验，我是赞同作者观点的，模式和原则，是软件工程设计的基石，肯定是高于库和框架，只不过我们普通企业或个人，自我设计框架和库不太现实，而且也需要从成本方面考虑，当然像 BAT 或者一些一线大厂，自己会设计很多框架和库。</p><p>因此，道理浅显易懂，但往往需要考虑实际场景。</p><p>2、<a href="https://www.theverge.com/2022/4/23/23036976/eu-digital-services-act-finalized-algorithms-targeted-advertising">谷歌、Meta 和其他公司将不得不根据新的欧盟立法解释他们的算法</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-4/post-002.jpg" alt="算法"></p><p>2022 年 4 月 23 日，经过数小时的谈判，欧盟同意了《数字服务法》（DSA）的广泛条款，这将迫使科技公司对其平台上出现的内容承担更大的责任。新的义务包括更快地删除非法内容和商品，向用户和研究人员解释他们的算法是如何工作的，并对错误信息的传播采取更严格的行动。</p><p>公司如果违规，最高面临年营业额 6% 的罚款！</p><p>在上一期周刊 <a href="https://www.panshenlian.com/2022/04/20/weekly-3/">《无聊科技正经事周刊（第3期）：美团的推荐算法，是在玩火吗？》</a> ，我简单说过美团等互联网平台的算法问题，以及国内的一些网络监管情况，感兴趣的可以看看。</p><p>总之，未来无论是国内外，线下有法律监管，线上也应该有法律监管，数字服务法案将重塑网络世界、监管网络世界。</p><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p>1985年，媒体评论家尼尔·波斯特曼写了一本书，名为《自娱自乐至死》。波斯特曼认为，<strong>媒体不强调信息的质量，而过分强调了我们对娱乐的渴望</strong>。 </p><p>我们不再被价值观、道德、忠诚或家庭所驱动，我们被媒体所强调的东西所驱动。所有媒体的首要主题是什么？关注富人和名人，这是唯一重要的事情。</p><p>–  <a href="https://dariusforoux.com/rich-and-famous/">《“为什么我不富有和出名？” 我们时代的错觉》</a></p><p>2、</p><p>我听过很多人谈论工作与生活的平衡，我认为这种情绪是善意的，但它忽略了一个关键的机会。经营一家公司真是太难了。即使在好的时候，它也可能是一种磨难。</p><p>腾出个人时间绝对是至关重要的，但你也应该考虑如何 <strong>将更多的生活融入到你的工作中</strong>，我喜欢将其视为工作与生活的融合。</p><p>–  <a href="https://future.a16z.com/managing-your-mental-health-while-running-a-startup/">在创业时管理你的心理健康</a></p><h2 id="首发订阅"><a href="#首发订阅" class="headerlink" title="首发订阅"></a>首发订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/weekly">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li></ul><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description><enclosure length="887655" type="application/pdf" url="https://ink.library.smu.edu.sg/cgi/viewcontent.cgi?article=5633&amp;context=sis_research"/><itunes:explicit/><itunes:subtitle>这里记录每周值得分享的科技内容，周三发布。 本刊开源（GitHub: senlypan/weekly），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</itunes:subtitle><itunes:summary>这里记录每周值得分享的科技内容，周三发布。 本刊开源（GitHub: senlypan/weekly），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</itunes:summary><itunes:keywords>科技周刊, 理性, 人工智能</itunes:keywords></item><item><title>无聊科技正经事周刊（第3期）：美团的推荐算法，是在玩火吗？</title><category>科技周刊</category><category>美团</category><category>算法</category><pubDate>Tue, 19 Apr 2022 16:02:12 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/04/20/weekly-3/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/cover.jpg"></p><p>欧盟正在建设一条价值 110 亿美元的穿越阿尔卑斯山的隧道。（<a href="https://interestingengineering.com/video/eu-tunnel-candinavia-mediterranean">via</a>）</p><h2 id="本周话题：美团的推荐算法，是在玩火吗？"><a href="#本周话题：美团的推荐算法，是在玩火吗？" class="headerlink" title="本周话题：美团的推荐算法，是在玩火吗？"></a>本周话题：美团的推荐算法，是在玩火吗？</h2><p><strong>美团算法推荐</strong></p><p>从今年3、4月份开始，周围陆续有男性朋友说自己的美团首页，频繁被推荐一些类 “色/情” 擦边的营销产品服务，还调侃说美团真懂自己，当然，我并没太在意。</p><p>但是，最近发现自己的美团首页也充斥着这类打着 “单身无人”、“男士专享”、“纯/欲”、“私/密空间”、“乳/式按摩” 、“宅/欲”、“解/压”、“技/师” 等极具诱导标签的营销产品服务。</p><p>于是我好奇的找了一个小范围的微信群，问了一句：“ <strong>@所有人</strong>  <strong>你们把美团的首页截图一下，看看有啥？</strong>“ </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-1.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-2.jpg"></p><p>结果惊呼，几乎所有男性伙伴们，在美团首页都能看到此类产品服务的推荐信息，我列贴部分：</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-002.jpg"></p><p>我相信大部分男性，今天打开美团首页，依然能看到此类推荐的产品服务。</p><p>当然，不止是 <strong>美团</strong>，很多互联网平台，依靠自身大流量优势，吸纳海量用户，收集用户个人信息、浏览记录、地理位置、交易行为、兴趣爱好、联系方式等等，进行最大程度的商业化利用。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-7.jpg"></p><p>在提供便利的同时，也出现了例如大数据“杀熟”、流量造假、诱导沉迷等不合理的问题给我们的生活增添不少烦恼。而更有甚者，通过算法推荐展示低俗信息吸引眼球、诱导过度消费、推荐类色/情信息诱导未成年，甚至使用一些突破人性道德伦理的营销方式来达成交易，这些行为，都是在法律边缘试探。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-010.jpg"></p><ul><li>针对已婚妈妈，过度推荐儿童消费</li><li>针对未成年，推荐游戏、甚至色/情类信息</li><li>针对老年人，推荐保健品、理财类</li><li>针对男性，当然就是以上的解压/放松、单身/按摩、私密/宅诱等等。</li><li>我们每天都在接收骚扰电话、你孩子姓名、年龄、学历等等都可能被恶意侵犯、利用。</li></ul><p>难道我们，只能眼睁睁看着自己的信息被互联网平台无限滥用和过度消费吗？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-3.jpeg"></p><p>当然不是！<a href="https://baijiahao.baidu.com/s?id=1726154077510563255">算法，需依法！</a></p><p>国家早已给 “算法” 立规矩！3月1日起，<a href="http://www.cac.gov.cn/2022-01/04/c_1642894606364259.htm">《互联网信息服务算法推荐管理规定》</a>正式实施。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-6.jpg"></p><p><strong>互联网清朗行动</strong></p><p>早在 2004 年 ，国家就陆续推出过许多管理行动通知以及管理规定，包括但不仅限于算法分级分类治理、网络安全、信息服务，以及算法推荐备案治理等。</p><p>一直以来，国家在加强互联网信息服务算法推荐管理，以及推动互联网信息服务算法综合治理专项行动方面做了很多工作。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-004.jpg"></p><ul><li><a href="http://www.cac.gov.cn/2022-02/25/c_1647395666889023.htm">关于互联网信息服务算法备案系统上线的通告</a></li><li><a href="http://www.cac.gov.cn/2022-03/01/c_1647766971713631.htm">加强算法风险全流程治理 创设算法规范“中国方案”</a></li><li><a href="http://www.cac.gov.cn/2022-03/02/c_1647826979822123.htm">专家解读｜规范算法推荐 发展科技法理</a></li><li><a href="http://www.cac.gov.cn/2022-04/08/c_1651028524542025.htm">关于开展“清朗·2022年算法综合治理”专项行动的通知</a></li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-003.jpg"></p><p>并且，重点检查具有较强舆论属性或社会动员能力的大型网站、平台及产品，包括：</p><ul><li>督促企业利用算法加大 <strong>正能量传播</strong></li><li>处置 <strong>违法和不良</strong> 信息</li><li>整治 <strong>算法滥用</strong> 乱象</li><li>积极开展 <strong>算法备案</strong></li><li>推动 <strong>算法</strong> 综合治理工作的常态化和规范化</li><li>营造 <strong>风清气正</strong> 的网络空间</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-8.jpg"></p><p>因此，美团等平台有可能确实在玩火、在打擦边球。（<a href="http://www.wyaq.com/youxi/zhishi/2080.html">via</a>）</p><p>但是，不管如何，我倒是好奇一个问题？未来这些大互联网平台的算法，我们普通人能够干预或参与吗？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-4.jpg"></p><p>恰巧，这两天网上很多人在讨论马斯克入股、收购 <a href="https://baike.baidu.com/item/Twitter/2443267">推特</a>，而其中主要在讨论一个很有意思的言论：“如果埃隆·马斯克能够以 430 亿美元成功收购 <a href="https://baike.baidu.com/item/Twitter/2443267">Twitter</a> ，他希望 Twitter 的算法能够公开，以确保平台公平“。简单来说，就是马斯克认为：<strong>Twitter 应该开放算法！</strong> 这个言论在引起很激烈的讨论，特别是在我们 IT 人群当中，更有意思的是，发明推特的人也认同这个想法！</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/topic-001-5.jpg"></p><p>然后，我发现有一篇文章 <a href="https://every.to/divinations/elon-is-right-twitter-should-open-up-the-algorithm">《马斯克是对的：Twitter 应该把算法开源》</a> ，还针对马斯克的观点做了一些实际分析。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-011.jpg"></p><p>作者认为如果开放算法，有利于增加透明度和为用户创造更多选择，将会是一件好事，并从几个维度去分析讨论：</p><ul><li>增加使用率</li><li>减少骚扰</li><li>重建信任</li></ul><p>我很赞同作者的观点，而且我认为，如果包括美团在内的互联网平台，未来都能开放算法，那对于苦互联网平台久矣的网民来说，绝对会是一件开创性的好事，虽然对像美团此类大平台并不需要再去考虑扩大用户规模、或者说让用户保持信任之类的行为（由于市场已经基本垄断），所以仅剩下的一个直接好处，那就是避免算法被平台滥用，导致我们受到无尽的骚扰与侵犯，导致我们裸露在毫无节制的各种算法当中，仅此而已。</p><p>基本，以上就是我简单的看法，不知道大家怎么想，对于如今大平台的算法与推荐，是否感到厌恶？</p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://baike.baidu.com/item/%E7%A5%9E%E8%88%9F%E5%8D%81%E4%B8%89%E5%8F%B7">英雄归来！中国神舟十三号载人飞船历时半年，完成创纪录任务后载3名机组人员安全降落</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-016.jpg"></p><p><strong>发射成功</strong></p><p>2021年10月16日0时23分，搭载神舟十三号载人飞船的长征二号F遥十三运载火箭在酒泉卫星发射中心按照预定时间精准点火发射，约582秒后，神舟十三号载人飞船与火箭成功分离，进入预定轨道，顺利将翟志刚、王亚平、叶光富3名航天员送入太空，飞行乘组状态良好，发射取得圆满成功。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015.jpg"></p><p><strong>在轨半年</strong></p><p>神舟十三号机组人员在轨道上度过了六个月。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-017.jpg"></p><p>神舟十三号任务将实现五个方面的任务目标，与神舟十二号任务相比，神舟十三号任务主要有以下几方面不同：</p><ul><li>一是载人飞船将采用自主快速交会对接的方式，首次径向停靠空间站；</li><li>二是届时中国空间站将实现核心舱、2艘货运飞船、1艘载人飞船共4个飞行器组合运行；</li><li>三是航天员将首次在轨驻留6个月，这也是空间站运营期间航天员乘组常态化驻留周期；</li><li>四是中国女航天员将首次进驻中国空间站，航天员王亚平也将会成为中国首位实施出舱活动的女航天员，而神舟十三乘组也将包括中国首次出舱的男女航天员；</li><li>五是在神舟十二号任务的基础上，进一步开展更多的空间科学实验与技术试验，产出高水平科学成果；</li><li>六是实施任务的飞船、火箭均在发射场直接由应急待命的备份状态转为发射状态。</li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-014.jpg"></p><p>神舟十三号飞船标识。</p><p><strong>生活与工作</strong></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-1.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-2.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-3.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-4.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-5.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-6.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-7.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-8.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-9.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-10.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-11.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-12.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-13.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-14.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-20.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-21.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-22.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-23.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-24.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-25.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-26.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-27.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-28.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-29.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-30.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-015-31.jpg"> </p><p><a href="http://www.xinhuanet.com/science/2022-04/15/c_1310559996.htm">via</a> </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-013.jpg"><br><strong>成功着陆</strong></p><p>2022年4月16日9时56分，神舟十三号载人飞船返回舱在东风着陆场成功着陆。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-012.jpg"><br>航天员翟志刚、王亚平、叶光富（左至右)安全顺利出舱 （<a href="http://paper.people.com.cn/rmrb/html/2022-04/17/nw.D110000renmrb_20220417_2-01.htm">新华社</a> )</p><p><strong>新起点，新记录</strong></p><p>神舟十三号载人飞行任务取得圆满成功， 中国航天，又站在了一个新的起点。 </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-011.jpg"></p><p>致敬航天英雄。</p><p>2、<a href="https://news.cision.com/chalmers/r/converting-solar-energy-to-electricity-on-demand,c3540525">一种新的储能系统可以将太阳能储存近二十年</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-001.jpg"></p><p><strong>转储太阳能</strong></p><p>瑞典查尔姆斯理工大学 Kasper Moth-Poulsen 教授团队设计了一种能源系统，可以将太阳能储存长达 18 年。</p><p>这项新技术基于查尔姆斯理工大学开发的太阳能系统 MOST——分子太阳能热能储存系统。这项技术是基于一种特殊设计的分子，当它与阳光接触时会改变形状，该研究在早期阶段提出时就已经引起了全世界的极大兴趣。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-002.jpg"></p><p><strong>按需释放储能</strong></p><p>瑞典研究团队把存储有太阳能的 MOST 材料（降冰片二烯），运到中国上海，经过中国上海交通大学胡志宇教授、李涛教授团队，成功利用 MEMS-TEG 发电芯片进行了 <strong>按需</strong> 放热发电的实验室演示，首次实现了分子太阳能至热能储存释放继而转换为电能。</p><p><strong>思考</strong></p><p>像我们平时给充电宝充满电，过一段时间再用发现没电了，蓝牙耳机也是如此，是否未来也能改用这类储能系统，让充电宝、蓝牙耳机等等，也能放上18年。</p><ul><li><a href="https://news.cision.com/chalmers/r/converting-solar-energy-to-electricity-on-demand,c3540525">瑞典查尔姆斯理工大学报道</a></li><li><a href="https://www.sciencedirect.com/science/article/pii/S266638642200056X">《细胞报告物理科学》杂志</a></li><li><a href="https://www.thepaper.cn/newsDetail_forward_17195963">CellPress细胞科学-CRPS：实现太阳能异地存储且按需发电的新型绿色能源技术</a></li></ul><p>3、<a href="https://www.visualcapitalist.com/radiation-emissions-of-popular-smartphones/">流行的智能手机发出多少辐射？</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-000.jpg"></p><p>智能手机已经成为我们日常生活中不可或缺的一部分，从工作和学校到日常任务，这些手持设备已将一切尽在掌握之中。大多数人每天在手机上花费5-6 个小时。而且，鉴于我们的手机会散发出微量的辐射，我们每天都会将自己暴露在辐射中数小时。</p><p>但是不同的手机发出的辐射量不同，在德国联邦辐射防护办公室收集的数据的帮助下，我们可视化了当今市场上一些流行的智能手机的辐射排放。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-001.jpg"></p><p><strong>摩托罗拉 Edge 2020</strong> 的辐射发射最高，SAR 值为每公斤1.79 瓦的辐射。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-002.jpg"></p><p>SAR值最低的智能手机是 <strong>中兴 Blade V10</strong>，每公斤辐射0.13瓦。</p><ul><li>虽然，目前没有重要的研究证明手机辐射的有害影响。</li><li>但是，长时间接触智能设备的人，至少可以量化他们的辐射暴露量，并选择哪些品牌可以满足他们的需求。</li></ul><p>4、<a href="https://interestingengineering.com/video/build-miniature-refrigerator">使用基本材料制作微型冰箱的方法</a>（英文，视频）</p><p>第 1 步：准备主要电子设备</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-004.jpg"></p><p>第2步：制作冰箱的主壳</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-005.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-006.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-007.jpg"></p><p>第 3 步：继续构建主冰箱</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-008.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-009.jpg"></p><p>第4步：完成冰箱</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-010.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/keji-003.jpg"></p><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://codepen.io/wyattnolen/pen/zYPWrdg">多边形动物变形（纯CSS）</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-003.jpg"></p><p><a href="https://codepen.io/wyattnolen">Wyatt Nolen</a> 创建了一个很酷的动画，它使用 div 叠层、路径裁剪和 CSS 动画，就实现了两个多边形动物之间的转换效果。</p><p>2、<a href="https://www.wpr.org/u-s-citizens-can-now-choose-gender-x-their-passport-applications">美国公民现在可以在护照申请中选择性别“X”</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-004.jpg"></p><p>美国增加第三个性别选项，除了以前可用的“F”和“M”选项外，申请护照的美国公民现在可以在他们的申请中选择性别“X”，以使护照对那些认定为双性、未指定或其他性别认同的人更具包容性。</p><p>3、<a href="https://pointerpointer.com/">超无聊的网站：鼠标指针指示器</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-005.png"></p><p>打开网站之后，移动鼠标，网站就会读取一张照片，照片里会有人的手指正好指着你的鼠标……</p><p>开发者真的太无聊了，哈哈哈哈哈。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-006.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-007.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-008.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/linggan-009.jpg"></p><p>虽然很无聊，但又觉得手指识别得很精准，反正我是玩了快20分钟。</p><p>当然，我建议你去玩一下：<a href="https://pointerpointer.com/">https://pointerpointer.com/</a></p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://www.csoonline.com/article/2117843/what-is-phishing-examples-types-and-techniques.html">什么是网络钓鱼？示例、类型和技术</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-002.jpg"></p><p>网络钓鱼是一种网络攻击，它使用伪装的电子邮件来诱骗收件人放弃信息、下载恶意软件或采取其他一些所需的行动。</p><p>作为最古老的网络攻击类型之一，网络钓鱼 <a href="https://www.computerworld.com/article/2575094/sidebar--the-origins-of-phishing.html">可以追溯到 1990 年代</a>，目前它仍然是最广泛和最有害的网络攻击之一，网络钓鱼攻击方式和技术也变得越来越复杂。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-003.jpg"></p><p>一些 <strong>轰动一时</strong> 的网络钓鱼诈骗：</p><ul><li><a href="https://en.wikipedia.org/wiki/2014_celebrity_nude_photo_leak">名人LUO照泄露</a></li><li><a href="https://www.cbsnews.com/news/the-phishing-email-that-hacked-the-account-of-john-podesta/">希拉里竞选团队邮件外泄</a></li></ul><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-001.png"></p><p>文章详细介绍了网络钓鱼的攻击类型、攻击原理以及相关防御手段。</p><p>2、<a href="https://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html">《请不要再将数据库称作CP或AP (Please stop calling databases CP or AP)》</a>（英文，推荐）</p><p>Jeff Hodges 在他那篇精彩的博客文章 <a href="https://www.somethingsimilar.com/2013/01/14/notes-on-distributed-systems-for-young-bloods/">Distributed Systems for Young Bloods（面向年轻人的分布式系统）</a> 中，建议我们用 CAP 定理来描述和评论系统。 很多人都听取了这个建议，描述他们的系统为”CP”、“AP” 或者有时候 “CA”。</p><p>而 <a href="https://martin.kleppmann.com/">Martin Kleppmann</a> 教授对 Jeff Hodges 关于 CAP 定理的观点持不同意见，他认为 CAP 定理过于精简，且被广泛误解，导致它对描述一个系统没有多大用处. 。因此 Martin Kleppmann 教授恳请大家最好还是放弃 CAP 定理并停止引用和讨论. 并且要用更精确的术语来权衡系统。</p><p>参与 <strong>分布式系统相关设计</strong> 的研发同学，应该关注这个话题。  </p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-004.jpg"></p><p>Martin Kleppmann 教授是我认为巨牛的一位教授、软件工程师和企业家，而且还出过音乐作品。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-005.jpg"></p><p>他出版的这本书 <a href="https://book.douban.com/subject/30329536/">《数据密集型应用系统设计》(中文版)</a> ，在国内豆瓣评分高达 9.7 分，本书几乎涵盖大部分分布式系统的应用设计场景，以及背后或底层的原理，深入浅出，初学者、高手，都会有所收获，特别推荐大家！</p><p>另外，本文你也可以通过中译版 <a href="https://zhuanlan.zhihu.com/p/55053121">《请不要再将数据库称作CP或AP》</a> 进行阅读。</p><p>3、<a href="https://spectrum.ieee.org/software-engineer-salary-2657117801">5 张图表了解 2022 年的软件工程工资（数据来源Hired）</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-006.jpg"></p><p>或许 <a href="https://hired.com/2022-state-of-software-engineers/">Hired 统计的数据</a> 无法代表国内的实际情况，但是技术岗位趋势，值得参考。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-007.jpg"></p><p>网络安全人才短缺持续加剧，后端工程师排在第七。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-008.jpg"></p><p>Go 语言成为最热门最受欢迎的技术技能。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-009.jpg"></p><p>Python 和  JavaScript 最受工程师的喜爱，Java 仅排第三。当然，如果把国内并入统计，很大可能就是 Java 语言最热门。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/article-010.jpg"></p><p>希望通过软件拯救世界的排名中，第一大领域是公共卫生，接着是思考教育、未来的工作和气候变化三大话题。</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://chalk.ist/">Chalk.ist：超颜值的源码图片生成器</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-001.jpg"></p><p>一款可以快速将您的源代码变成漂亮图片的工具。支持自定义主题、样式、边距等。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-003.jpg"></p><p>目前支持的语言包括 Json、YAML、HTML、Markdown、Twig、JavaScript、TypeScript、CSS、SCSS等。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-002.jpg"></p><p>我小试一下，代码编写还支持语法提示，很良心的一款高颜值源码图片生成器，点一个赞。</p><p>当然如果你不喜欢这种图像风格，可以尝试这款 <a href="https://carbon.now.sh/">Carbon</a> ，相比 Chalk.ist ，Carbon 提供了更高粒度的定制化，试试吧，或许你会喜欢。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-004.jpg"></p><p>2、<a href="https://khushmeen.com/icons.html">Doodle Icons：400 多个免费手绘图标</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-005.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-006.jpg"></p><p>Doodle Icons 是一款很特别的手绘图标，可以在 PNG 和 SVG 格式以及 Figma 中使用，提供 15 类图标，可以在任何类型的项目中免费使用。</p><p>3、<a href="https://giscus.app/zh-CN">giscus：基于github的评论系统</a> （10分钟就集成到我的<a href="https://www.panshenlian.com/">个人网站</a>）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-007.jpg"></p><p>giscus 是基于 github 的一款开源评论系统，基本有以下几个特性：</p><ul><li>开源。🌏</li><li>无跟踪，无广告，永久免费。📡 🚫</li><li>无需数据库。全部数据均储存在 GitHub Discussions 中。:octocat:</li><li>支持自定义主题！🌗</li><li>支持多种语言。🌐</li><li>高度可配置。🔧</li><li>自动从 GitHub 拉取新评论与编辑。🔃</li><li>可自建服务！🤳</li></ul><p>对于开放型的网站而言，giscus 是个不错的选择，特别是像我这类开源项目基本都托管在 <a href="https://github.com/senlypan">github</a> 的人，就特别合适。根据 <a href="https://giscus.app/zh-CN">giscus 使用指南</a> ，前前后后花了我不到10分钟的时间，完成了集成、调试、上线发布，下图是我的 github 启用评论系统后的效果。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-009.jpg"></p><p>至于我们网站集成之后的效果，也很简单，指定 .class 容器集成就可以，个人还挺满意，你可以来 <a href="https://www.panshenlian.com/weekly/">我的网站</a> 看看效果吧，如下图，支持点赞评论等。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-008.jpg"></p><p>不过 <strong>缺点</strong> 也很明显，就是用户只能通过 github 账号登录并评价。当然！如果你和我一样是技术类的网站，那其实问题不大，github 账号都是人手一个，所以也能接受。</p><p>4、<a href="https://www.joshwcomeau.com/gradient-generator/">Gradient Generator：一个在线  CSS 渐变效果生成工具</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-010.jpg"></p><p>一个在线工具，可帮助您生成可在 CSS 中使用的丰富、漂亮的渐变效果。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/tools-011.jpg"></p><ul><li>支持直接复制 CSS 代码</li><li>支持把已设计好的 CSS 代码通过 URL 链接进行分享</li></ul><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://hackernoon.com/nodejs-vs-net-core---which-is-the-ultimate-server-side-development-platform">NodeJS 与 .NET Core——哪个是终极服务器端开发平台？</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-002.jpg"></p><p>2022 年最流行的两个服务器端开发平台是 Node.js 和 .NET Core。</p><p>本文就 Node.js 和 .NET Core 的语言特性、核心优势、使用场景、性能表现、安全性、生态以及差异性等方面进行了讲解比对。</p><p>2、<a href="https://www.mssqltips.com/sqlservertip/7217/functions-in-python-code-samples/">15 个重要的 Python 内置函数</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-001.png"></p><p>文中介绍了 Python 中的各种内置函数，这些函数在使用 Python 时非常有用，例如 iter、len、help、hash、map、print 等等。</p><p>3、<a href="https://hackernoon.com/filebase-building-web3-with-web3">Filebase：使用 Web3 构建 Web3</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-003.jpg"></p><p>Filebase 是世界上第一个由去 <strong>中心化存储网络</strong> 提供支持的对象存储平台，将多个网络统一在一个与 <a href="https://baike.baidu.com/item/s3/1409766?fr=aladdin">S3</a> 兼容的 API 下，使去中心化存储易于访问和使用。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-004.jpg"></p><p>国内使用 Filebase 结合 Amazon S3 可能不太允许，但是去中心化存储方案或许会给我们带来一种不同的思考方式。这是 <a href="https://docs.filebase.com/">Filebase 文档</a> 。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-005.jpg"></p><p>4、<a href="https://basicappleguy.com/basicappleblog/macbook-pro-schematics">MacBook Pro 的真实结构图壁纸</a></p><p>桌面壁纸上直接展示透视之后的真机结构的想法其实见过蛮多了，这个 M1 MBP 的版本也是这个思路，不过做得还挺严谨，14 寸和 16 寸机型的壁纸都不一样，看似是按照实际来的~</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-006.jpg"></p><p>M1 Pro（在银色 14 英寸 Pro 上显示）壁纸</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-009.jpg"></p><p>M1 Max（在 16 英寸深空灰色 Pro 上显示）壁纸</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-010.jpg"></p><p>MacBook Pro 上的每个风扇都由 90 个不同的组件组成</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-007.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-008.jpg"></p><p>打开 <a href="https://basicappleguy.com/basicappleblog/macbook-pro-schematics">MacBook Pro 的真实结构图壁纸</a> 看看吧，严谨精致。</p><p>5、<a href="https://bevyengine.org/news/bevy-0-7/">Bevy 0.7：用 Rust 构建的面向数据的游戏引擎</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-011.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/source-012.jpg"></p><p>Bevy 0.7 是一款用 Rust 构建的面向数据的游戏引擎，永远免费和开源。您可以在 GitHub 上获取完整的 <a href="https://github.com/bevyengine/bevy">源代码</a>，或者可以查看 <a href="https://bevyengine.org/learn/book/getting-started/">快速入门指南</a> 开始使用。</p><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://www.odditycentral.com/foods/japanese-restaurant-goes-viral-for-serving-dessert-shaped-like-pieces-of-plaster.html">日本餐厅因提供像石膏一样形状的甜点而风靡一时</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-001.jpg"></p><p>东京银座皇家花园酒店的 Opuses 餐厅最近因为一种形状像墙灰泥的有趣甜点而引起了很多关注。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-002.jpg"></p><p>Opuses 甜点实际上是由覆盖着这些石膏状蛋白酥皮的冰淇淋组成，虽然不太像冰淇淋。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-003.jpg"></p><p>对比一下我们国内的炒酸奶</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-004.jpg"></p><p>2、<a href="https://www.nature.org/en-us/get-involved/how-to-help/photo-contest/2021-winners/">欣赏大自然保护协会 2021 年摄影大赛的精彩获奖者</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-007.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-008.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-005.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-006.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-009.jpg"><br><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/photo-010.jpg"></p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/393522524">和Vue.js的创造者尤雨溪聊开源软件</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/boke-001.jpg"></p><p>开源软件影响了我们生活的方方面面，从我们手机的安卓操作系统，到上网使用的 Chrome 浏览器；从前端开发者使用的 React, Vue，再到后端进行大数据存储和处理的 Hadoop , 以及人工智能的 Tensorflow , MXNet 等等。</p><p>这期播客邀请到了 Vue.js 的创造者，也是全球开源社区的领军人物 <a href="https://twitter.com/youyuxi">尤雨溪（Evan You）</a> 来和大家聊一聊开源软件这个话题。</p><p><a href="https://github.com/avocadotoastlive/avocadotoast.live">牛油果烤面包</a> 也是个开源的项目，音乐播客也开源！</p><p>2、<a href="https://www.ximalaya.com/sound/520233485">互联网是否欺骗了我们</a>（中文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/boke-002.jpg"></p><p>本期播客，相征迎来了他的一位老朋友，对于大家来说，也是一位新朋友。如果你喜欢听播客，又恰好喜欢音乐，多少一定有过耳闻的这位—重轻老师。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/boke-005.jpg"></p><p>他广为人知的身份，是机核电台 GADIO 的常驻嘉宾。同时，重轻也是不定期更新的播客《不在场》的主理人、音乐科技领域的 UP 主。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/boke-006.jpg"></p><p>本期节目，这俩互联网 “punk 主义” 爱好者，就从当下大热的 <a href="https://baike.baidu.com/item/%E8%89%BE%E5%B0%94%E7%99%BB%E6%B3%95%E7%8E%AF/57233820">《艾尔登法环》</a> 聊起，咱们坐下来，好好掰扯掰扯，这互联网到底是工具，还是满是腥臊味儿的大浴缸。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/boke-003.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/boke-004.jpg"></p><p>你打开手机，就是为了舒服一下？在工具的恶和对人类的反向塑造的同时，我们真的手无寸铁、束手就擒了吗？那个体究竟还能做点儿什么来对抗着看不见的巨物和网络呢？</p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://wx.zsxq.com/dweb2/index/group/88855458825252">《开源的 Why 和 How 》</a> （付费知识星球：云谦和他的朋友们）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/post-004.jpg"></p><p>（ <a href="https://sorrycc.com/">陈成</a>，花名云谦，阿里 &amp; 蚂蚁 10 年 + 老前端，ZJU，P8，Umi &amp; Dva 等开源库作者，蚂蚁中台框架 Owner，「MDH 前端周刊」作者，初级路亚爱好者，游戏收藏师。）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/post-003.jpg"></p><p>《开源的 Why 和 How 》文章主要分享了两个主题：</p><ul><li>为什么做开源？</li><li>怎么做开源？</li></ul><p>开源的驱动力最开始可能来自于个人分享欲以及获得的成就感，但回过头看，开源相比闭源的投资回报率高出很多，包括社区、影响力、代码、人才、需求、灰度等方面都有不同的收获，老外对此有个的定义，叫 <strong>「工程经济学」</strong>。</p><p>陈成老师认为优秀的开源包括三个技能点：</p><ul><li>代码</li><li>文档</li><li>社区</li></ul><p>过硬的代码、丰富的文档，以及活跃的社区，按照 4:3:3 的时间精力分配，能让一个优秀的开源项目保持在一定的健康状态。</p><p>当然，仅推荐前端开发或全栈开发的伙伴关注订阅，其他专注后端研发等同学暂不推荐订阅该知识星球（由于该知识星球主要涉及前端技能、知识、方法论、工作、学习、以及招聘面试晋升等）。</p><p>如果你希望了解更多关于开源的话题，或许你可以看看知乎的一个圆桌收录：</p><ul><li><a href="https://www.zhihu.com/question/269033309">为什么程序员们愿意在GitHub上开源自己的成果给别人免费使用和学习？</a> </li></ul><p>2、<a href="https://www.cio.com/article/190888/5-famous-analytics-and-ai-disasters.html">7 种著名的分析和 AI 灾难</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/post-001.jpg"></p><p>2017 年，<a href="https://www.economist.com/leaders/2017/05/06/the-worlds-most-valuable-resource-is-no-longer-oil-but-data">《经济学人》宣布</a>，数据已超石油，成为世界上最宝贵的资源。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-3/post-002.jpg"></p><p>各个行业的组织已经并将继续在数据和分析方面进行大量投资，但就像石油一样，数据和分析也有其阴暗面。</p><p>从机器学习算法驱动的分析和行动中获得的洞察力，可以为组织带来竞争优势，但在声誉、收入甚至生命方面，一些错误可能需要付出高昂的代价。</p><p>以下是过去十年中一些备受瞩目的分析和人工智能错误，以说明可能出现的问题。</p><ul><li>AI 算法能识别一切，而 COVID-19 例外</li><li>Zillow 减记数百万美元，由于算法购房灾难而裁员</li><li>英国因超出 Microsoft Excel 中的数据限制而丢失近 16,000 例冠状病毒病例</li><li>医疗保健算法未能标记黑人患者</li><li>数据集训练微软聊天机器人吐出种族主义推文</li><li>亚马逊人工智能招聘工具只推荐男性</li><li>目标分析侵犯隐私（针对怀孕预测进行营销）</li></ul><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p>人们常说，互联网使教育民主化：人类的所有知识都可以通过谷歌搜索到！然而，获得信息只是成功了一半，您还需要将原始信息转换为可用的技能。</p><p>学习如何有效地学习非常重要，尤其是作为软件开发人员，学习新事物实际上就是我们必须具备的！如果您能学会快速掌握新的语言、框架、工具，那么您将能够比普通开发人员更有效率。</p><p>这是一种超能力，这种超能力包括但不仅限于：主动试错、心态培养、目标动机、间隔重复、习惯养成、公开学习、技能网络 …</p><p>– <a href="https://www.joshwcomeau.com/blog/how-to-learn-stuff-quickly/">《如何快速学习东西》</a></p><p>2、</p><p>互联网做得太出色了，以至于很多人把它看做某种像太平洋一样的自然资源，而非人造的。你还记得上一个达到如此规模而又如此健壮的技术是什么？</p><p>– <a href="https://complexevents.com/2012/07/16/interview-with-alan-kay/">Alan Kay，Dr.Dobb 杂志采访（2012）</a></p><h2 id="首发订阅"><a href="#首发订阅" class="headerlink" title="首发订阅"></a>首发订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，首发在</p><ul><li><a href="https://www.panshenlian.com/weekly">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li><li>其他平台会延后一周发布</li></ul><p>微信搜索“第一次当爸爸吖”或者扫描二维码，即可订阅。</p><p><img src="https://www.panshenlian.com/images/post/wechat/v3.jpg"></p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description></item><item><title>无聊科技正经事周刊（第2期）：线上马拉松你会参加吗？</title><category>科技周刊</category><category>马拉松</category><pubDate>Wed, 13 Apr 2022 01:55:17 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/04/13/weekly-2/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/cover-001.webp"></p><p>世界上最大的悍马（悍马 H1 X3 ）让普通的悍马看起来像一辆玩具车。（<a href="https://www.odditycentral.com/auto/worlds-largest-hummer-makes-the-regular-one-look-like-a-toy-car.html">via</a>）</p><h2 id="本周话题：线上马拉松你会参加吗？"><a href="#本周话题：线上马拉松你会参加吗？" class="headerlink" title="本周话题：线上马拉松你会参加吗？"></a>本周话题：线上马拉松你会参加吗？</h2><p>据 <a href="http://www.athletics.org.cn/news/marathon/2020/0501/346438.html">中国田径协会官方网站</a> 统计数据显示，2019 年全国范围内共举办马拉松规模赛事 1828 场（800人以上路跑、300人以上越野及徒步活动)，平均每天有 5 场马拉松赛事。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/topic-001.jpg"></p><p>排除疫情影响，按照这个数据增长趋势，2019年之后依然会保持着快速发展，原因可能会有这么几个：</p><ul><li>国家对马拉松运动产业市场的支持</li><li>媒体的关注</li><li>城市名片的打造</li><li>赛事运营机构/跑步协会/跑团进入</li><li>龙头企业和中小企业资本赞助进入</li><li>运动APP、户外运动产业链进入</li><li>更多</li></ul><p>总之，基于国家政策的支持、赛事的成熟运营、媒体的大力推广以及资本和技术的相继助力等等，马拉松赛事已经成为运动热潮。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/topic-002.jpeg"></p><p>但是很不幸，2019 年爆发了新冠疫情，极大的限制了线下马拉松赛事的开展，大部分计划赛事延期或取消。从 2020 年至今，我们有相当一部分马拉松赛事迁移到了线上举办。</p><p>关于线上马拉松，接受它的人认为它不限时间、不限地点，减低了参与门槛，提供了一个挑战自己的机会，也能让自己不断尝试新的跑马乐趣，当然有些人仅仅是因为喜欢马拉松的奖牌（例如我）。</p><p>但是不接受线上马拉松的跑友觉得线上马拉松缺少竞技氛围和观众气氛、失去了体育精神的意义，线下的赛道、奖牌、合影、城市才是马拉松的灵魂。</p><p>对此，你有何想法，是否接受线上马拉松比赛可以作为马拉松赛事的补充和延展呢？</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/topic-004.jpg"></p><p>反正我是线上线下都报名了，你会参加线上马拉松吗？</p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://www.theguardian.com/technology/2022/apr/04/mind-blowing-ai-da-becomes-first-robot-to-paint-like-an-artist">认识艾达（Ai-Da），第一个可以像艺术家一样画画的机器人</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/keji-001.png"></p><p>艾达（Ai-Da）是一个可以创作传统绘画的人形机器人，被誉为世界上第一个超现实机器人艺术家。艾达能够利用她的仿生手和眼睛中的相机通过铅笔等进行创作。</p><p>在2019年被创建，创建它的团队成员包括程序员、机器人专家、艺术专家和心理学家等。艾达可以绘画、素描、雕塑和创作诗歌，它将于4月22日在《2022年威尼斯双年展》上首次举办个展。</p><p>威尼斯双年展是一个以建筑、电影、音乐、舞蹈、戏剧和视觉艺术为特色的国际展览，今年是第 59 届，你可以在 <a href="https://www.labiennale.org/en/art/2022">网站</a> 上购票。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/keji-003.png"></p><p>而艾达（Ai-Da）也将成为该节日 120 年历史上首位机器人艺术家。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/keji-002.png"></p><p>AI-Da目前3岁了，在 TikTok 或者 <a href="https://www.ai-darobot.com/">AI-Da官网</a> 都能够看她很多艺术作品。</p><p>2、<a href="https://interestingengineering.com/elon-musk-tesla-lithium-mining">特斯拉可能会开拓一个新行业：锂矿</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/keji-004.jpg"></p><p>特斯拉CTO马斯克周五在推特上表示，他的公司可能会直接涉足锂矿开采和精炼业务，因为锂是电动汽车电池制造的关键组成部分，其成本已经变得 “疯狂” 。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/keji-005.png"></p><p>这是过去 20 年中每年每吨锂的平均价格，仅去年一年，锂的涨幅就超过了 480%。</p><p>但事实是，全球已探明的锂储量并不稀缺，根据美国地质调查局（USGS）的数据，截至2019年，全球已确定储量约为8000万吨。锂储量最大的六个国家，分别是：玻利维亚 - 2100万吨、阿根廷 - 1700万吨、智利 - 900万吨、美国 - 680万吨、澳大利亚 - 630万吨、中国 - 450万吨。</p><p>在地球上，锂几乎无处不在，只不过提取和提炼的速度很慢。</p><h2 id="灵感"><a href="#灵感" class="headerlink" title="灵感"></a>灵感</h2><p>1、<a href="https://codepen.io/fariati/pen/oNpZRbd">Minecraft 3D CSS 蜜蜂🐝</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/linggan-001.jpg"></p><p>Ibrahim Fariat 使用 CSS 创建了一个令人惊叹的动画场景。</p><p>网站展示了所有源码，包括HTML、CSS、JavaScript。</p><p>2、<a href="https://thehustle.co/04062022-brandon-sanderson-fantasy/">奇幻小说如何打破 Kickstarter 的记录</a>（英文）</p><p>Kickstarter于2009年4月在美国纽约成立，是一个专为具有创意方案的企业筹资的众筹网站平台。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/linggan-002.png"></p><p>奇幻作家 Brandon Sanderson 刚刚打破了 Kickstarter 上赞助者资助最多的项目纪录，共获得18.5万多赞助者，总额达到4170万美元。</p><p>对比之前CNBC的数据，2015年智能手表的价格才达到了2030万美元。</p><p>Kickstarter 的前出版主管 Margot Atwell 将 Sanderson 的成功归因于：</p><ul><li><strong>一个好的活动</strong>。有引人入胜的视频、吸引人的奖励，以及支持者何时收到奖励的明确时间表。</li><li><strong>对作家 Brandon  Sanderson 的信任</strong>。他拥有热情的粉丝群，并且在他的流派中广为人知。</li><li><strong>算法和互联网炒作</strong>。当支持者登录和分享时，项目可能会在 Kickstarter 上火爆，从而导致平台向其他人推荐该项目。</li></ul><p>从结果来看，互联网和粉丝经济依然很香。</p><p>3、<a href="https://www.quantamagazine.org/zugzwang-in-chess-math-and-pizzas-20220222/#newsletter">Zugzwang 在国际象棋、数学和披萨中的秘密</a> （英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/linggan-003.jpg"></p><p>在大多数两人游戏中，通常最好是先手赢后手输（先发制人的优势）。例如你和某人分享一个比萨，并且想吃更大的份，通常最好先拿第一片，并且先挑一个大的。包括网球比赛也都存在先手优势。</p><p>但在某些情况下，你后手也能赢得比赛。例如在国际象棋中有一个听起来很有趣的单词：Zugzwang。Zugzwang 是一个德语单词，字面意思是“强制移动”，例如下棋的时候，你被迫走了一些不得不走的步子，否则就会陷入危险，这个时候就叫 Zugzwang ，强制移动。</p><p>所以如果你利用强制移动的原则，再把游戏规则转变为数学思维，你就能学习到如何在对手先手的情况下还能赢得比赛。例如你有4份披萨，份量分别是 1、3、1、3，然后你需要想办法或指定规则等等，让你的对手先手并且只能先拿1，然后你第二次拿3，对手再拿1，最后你拿3，结果是1+1:3+3，即2:6，结果你后手并且赢得比赛。</p><p>虽然我觉得在现实中可能不会发生这么烧脑的分配披萨玩法，并且乐意接受这种玩法的对手，但是这种数学化的思维，很值得我们学习，特别是平时遇到的很多难题，其实都可以通过抽象思考、多维分析，以及数学化的方式去解决。</p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="https://retrocomputing.stackexchange.com/questions/24179/what-was-the-first-object-oriented-programming-language">第一种面向对象的编程语言是什么？</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-001.png"></p><p>在 stackexchange 上有个热门的问答，关于 “第一种面向对象的编程语言是什么？”。</p><p>关于OOP基本时间线是 ：</p><ul><li>1950 年代初开始有特定属性和关系的对象的概念</li><li>1958 年<a href="https://en.wikipedia.org/wiki/Lisp_(programming_language)?wprov=srpw1_1">LISP</a> 语言首次实现并支持任意复杂数据对象的语言</li><li>1960 年<a href="https://zh.wikipedia.org/zh-hans/Simula">Simula</a> 67 版本做了所有与 OOP 相关的功能，包括类、子类、方法、继承等。</li></ul><p>我是大学期间（2009~2013）才接触OOP，基本是 Delphi 和 C++，但是C++当时学得很混，所以只对 Delphi  有比较深刻的实践体会。</p><p>你是什么时候开始的接触OOP的？</p><p>2、<a href="https://stackoverflow.blog/2022/04/07/you-should-be-reading-academic-computer-science-papers/">你应该阅读学术计算机科学论文</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-002.jpg"></p><p>作为一名程序员，你需要不断地学习。可以查看教程、文档、公众号、博客网站、Stack Overflow 问题，或者查看任何可以帮助你编写代码和提升技能的内容。</p><p>虽然教程可以帮助您立即编写代码，但学术论文可以帮助您了解编程的起源和发展方向。</p><p>阅读研究论文不仅仅是了解历史，你可以通过阅读当前的研究找到解决问题的新方法。</p><p>你看教程、文档、公众号、博客网站、Stack Overflow 问题，只不过是以前有人遇到过与你相近的问题，并提供了一套大概的解决方案，但是通过看学术论文，你能够了解到以前的人为什么怎么想这个问题。因此，你应该阅读学术计算机科学论文，深刻理解本质。 </p><p>关于计算机科学论文，我个人认为肯定是优先国外的论文网站，这个领域，等你看到中文版时，可能已经是旧闻了。</p><p>这里我推荐一篇文章专门介绍 <a href="https://chriszheng.science/2016/06/28/How-to-find-scientific-papers/">《如何查文献》</a> （中文），文章中也介绍了几个不错的论文网站，例如通用型的谷歌学术、SCI论文类的Web of Science等，或许你能找到宝藏。</p><p>3、<a href="https://www.visualcapitalist.com/cp/big-mac-index-purchasing-power-parity-burger-inflation/">巨无霸指数：衡量购买力和通胀指标</a>（英文）</p><p>巨无霸于 1967 年由宾夕法尼亚州麦当劳特许经营权所有者 Jim Delligati 创建。它于次年在美国各地推出，如今您可以在 70 多个国家/地区购买。但是，正如巨无霸指数所示，具体支付的价格会根据所在的位置而有所不同。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-003.jpg"></p><p>鉴于麦当劳是世界上最大的公司之一，而且巨无霸在全球范围内广泛使用，这意味着著名的汉堡可以作为大多数国家之间的基本商品比较，并且它也具有相同的投入和分配系统的优势，仅仅在某些地方做了小修改（比如印度是鸡肉饼而不是牛肉）。</p><p>使用两个国家的巨无霸价格，该指数可以指示一种货币是否可能被高估或低估。例如，巨无霸在中国售价 24.40 人民币，在美国售价 5.81 美元。通过比较隐含汇率和实际汇率，可以看出人民币是高估还是低估。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-004.png"></p><p>根据巨无霸指数，人民币被低估了34%，即是相对美国卖便宜了，因为实际兑换汇率是6.37。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-005.png"></p><p>当然，它还能显示汉堡价格随时间变化了解通货膨胀情况。</p><p>瑞士的巨无霸是最昂贵，紧随其后的是挪威。与其他国家相比，这两个国家的物价水平都相对较高，但工资也较高。</p><p>委内瑞拉的汉堡价格涨幅最大，自 2004 年以来，巨无霸的成本上涨了近 250%。该国多年来一直受到恶性通货膨胀的困扰，因此该国数据中出现大幅价格波动也就不足为奇了。</p><p>最后，值得注意的是，俄罗斯拥有最便宜的巨无霸，反映了该国较低的价格水平。俄罗斯的劳动力成本大约是瑞士的三分之一。</p><p>当然，很多经济学家认为汉堡经济学是有局限性，基本观点是：</p><ul><li>受不同国家/地区的税率、劳动力成本等因素影响价格。</li><li>麦当劳并不是在全世界每个国家都有。</li><li>巨无霸指数缺乏多样性，该指数仅参考巨无霸，缺乏其他经济指标的多样性，例如消费者价格指数等。</li></ul><p>4、<a href="https://hackernoon.com/a-tour-of-the-web-web-10-web-20-and-web-30-explained">探索Web三代技术：Web 1.0、Web 2.0 和 Web 3.0</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-006.jpg"></p><p>这些年，无论作为普通用户还是技术研发人员来说，网络都发生了巨大的变化，由于网络的进步，制作更加有吸引力的网站或者应用程序，对各方面技术的要求越来越高，从web1.0的web诞生革命、到web2.0的成熟应用、再到web3.0的未来探索，人们对网络的进步的渴望，以及对技术的追求从未停止。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-008.jpg"></p><p>Web1.0 前身</p><p>超文本的概念比万维网早了几十年。1990 年 12 月 ，Tim Berners-Lee 希望开发一个可以跨网络工作的系统，允许个人从一台机器上的一个文件链接到另一台机器上的另一个文件。所以他用 <a href="https://baike.baidu.com/item/Objective-C/8374012">Objective-C</a> 编写了一个应用程序，并将其命名为 <a href="https://baike.baidu.com/item/www/109924">万维网</a> ，你可以在 <a href="https://worldwideweb.cern.ch/">这里</a> 访问第一个万维网的样子。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-007.jpg"></p><p>Web 1.0 是万维网革命的第一阶段，通常被称为 <strong>只读网络</strong>。网站是信息性的，仅包含超链接在一起的静态内容，或者简单地说，没有 CSS、动态链接、交互性（如登录用户、对博客文章的评论等）。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-009.jpg"></p><p>Web 2.0 从 2004 年至今，是万维网革命的第二阶段，通常称为 <strong>读写网络</strong>。重点是用户生成的内容、易用性、参与性文化和互操作性。Web 2.0 带来了根本性的转变，人们可以通过各种在线工具和平台分享他们的观点、意见、想法和经验。</p><p><strong>但是</strong>，Web2.0包括之前的技术，有一个致命的缺陷，那就是所有数据都存储在由公司或个人控制的数据库中。他们可以通过机器学习分析我们每个人的行为喜好和交易信息，并且还能曝光出售我们的个人信息和行为喜好，就像你每天都能接收到销售骚扰电话一样，令人担忧。</p><p>因此，人们开始追求一个去中心化且安全的互联网。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-010.jpg"></p><p>Web 3.0 概念旨在创建一个去中心化但安全的互联网，人们可以在其中安全地交换金钱和信息，而无需中间商或大型科技公司。</p><p>当然，Web3.0 和区块链都还处于早期发展阶段，未来要走的路还很长。不过，相信很多疑虑和难题，都会在前进中被消除。你会拥抱 Web3.0 吗？</p><p>5、<a href="https://devopedia.org/linux-signals">一篇很好的讲述Linux信号的文章</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-011.jpg"></p><p>Linux 计算机系统有许多不同状态的进程，这些进程属于用户应用程序或者操作系统。我们需要一种机制让操作系统内核与这些应用程序进程之间进行通信以协同配合。一种方法就是让流程发生一些重要事件时通知其它进程，这就是我们需要设计信号的原因。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/article-012.jpg"></p><p>关于 Linux 信号有那些、信号如何影响 Linux 进程、阻塞和解除阻塞信号，以及信号的典型生命周期等问题，你都可以在文章中了解到。</p><p>图文并茂，你应该会有收获。</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://toonme.com/">TOONME.COM</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-008.jpg"></p><p>可以在线将上传的照片转换为可爱的卡通图片，在延续了原图造型的基础上，重新利用 AI 绘制出不同风格的卡通形象。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-009.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-010.jpg"></p><p>至于网站功能评测，你们自己看效果吧，我简单上传了一张自己的图片（丑图），<a href="https://toonme.com/">TOONME.COM</a> 给我整得反正挺满意，效果秒杀国内一众卡通软件。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-011.jpg"></p><p>那高颜值的你们，还等什么呢？上手吧。</p><p>2、<a href="https://avvvatars.com/">Avvvatars</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-001.png"></p><p>Avvvatars 是一组制作精美的独特头像占位符，可作为 React 包和 Figma 库使用。</p><p>3、<a href="https://fakerjs.dev/">Faker</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-002.svg"></p><p>一款实用工具，可以生成大量虚拟（但合理）数据，主要用于测试和开发，并加快前端研发效率，基本用途有：单元测试、 性能测试、构建演示，特别是在没有完整后端的情况下前端进行实用。</p><p>Faker 支持在Nodejs、Perl、Ruby、Java、Python。</p><p>4、<a href="https://www.infoworld.com/article/3195951/review-the-10-best-javascript-editors.html">10 个最好的 JavaScript 编辑器</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-003.png"></p><p>Sublime Text、Visual Studio Code、Brackets 和 Atom 位居榜首，但其他几个也值得考虑。</p><p>至于为什么不适用 IDE ，最直接的原因那就是 <strong>运行速度</strong> ！</p><p>当然，如果你有更高的编程需要，那可能你需要  <a href="https://www.infoworld.com/article/3192844/review-the-6-best-javascript-ides.html">《6 款最好的 JavaScript IDE 工具》</a>。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-004.png"></p><p>5、<a href="https://excalidraw.com/">Excalidraw Editor</a></p><p>Excalidraw 是一种流行的在线绘图工具，用于创建“手绘”图表，其最酷的功能之一是能够以 SVG 格式导出，然后在您的页面中无损使用。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-005.jpg"></p><p>目前 VS Code 扩展已经支持，成为官方的 VS Code Excalidraw 扩展插件，如果你也跟我一样使用 VS Code 编辑器，那去这个 <a href="https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor#excali">网站地址</a> 下载 VS Code Excalidraw 扩展插件地址吧。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-006.png"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/tools-007.png"></p><p>我简单尝试了一下网页版：好用、美观、免费导出。</p><p>平时喜欢手绘的伙伴，上车吧 ~</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://www.networkworld.com/article/3656629/10-essential-linux-security-tools-for-network-professionals-and-security-practitioners.html">网络和安全专家的 10 个必备 Linux 工具</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/source-001.webp"></p><p>RedHat 产品安全副总裁 Vincent Danen和其他安全专家，推荐了10款网络安全方面必备的 Linux 安全工具。</p><p>居多都是开源免费的工具，基本涵盖监控、破解、测试、攻击、网络隧道、嗅探、扫描、映射等，并且适用于大部分环境，例如 Wi-Fi 网络、Web 应用程序、数据库服务器等。</p><p>2、<a href="https://github.com/pipipi-pikachu/PPTist">开源在线幻灯片平台</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/source-002.png"></p><p>PPTist： 一个功能丰富的在线幻灯片应用，可基于此搭建自己的在线幻灯片平台。</p><p>一个基于 Vue3.x + TypeScript 的在线演示文稿（幻灯片）应用，还原了大部分 Office PowerPoint 常用功能，支持 文字、图片、形状、线条、图表、表格、视频、公式 几种最常用的元素类型，每一种元素都拥有高度可编辑能力，同时支持丰富的快捷键和右键菜单，支持导出本地PPTX文件，您可以在此基础上搭建自己的在线幻灯片应用。</p><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://www.odditycentral.com/animals/this-short-beaked-google-eyed-pigeon-breed-looks-like-a-real-life-bird-caricature.html">一种罕见的鸽子品种看起来像漫画鸟</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/photo-001.webp"></p><p>布达佩斯短脸鸽（Budapest Short Face Tumblers）可以不间断飞行长达 5 小时，飞行距离约为 800 公里。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/photo-002.jpg"></p><p>布达佩斯短脸鸽（Budapest Short Face Tumblers）的耐力是它的象征，但第一次看到它的人并不太关心它的飞行能力，而更关心它的独特外观。小脸和小嘴，与凸出的蛙眼形成鲜明对比，让人既好奇又害怕，但也引起了人们的注意。</p><p>2、<a href="https://shkspr.mobi/blog/2022/04/this-bench-does-not-exist/">不存在的板凳</a> （英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/photo-003.jpg"></p><p>作者通过人工智能生成了数千长椅子，首先让程序模型了解椅子长什么样子，然后经过训练之后生成不同场景下的椅子，虽然有些看起来很魔幻不合逻辑，甚至说是恐怖，但是我们能看出来是一张椅子。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/photo-004.jpg"></p><p>另外还提供了一个 300MB 的 StyleGAN 神经网络 <a href="https://g-75671f.f5dc97.75bc.dn.glob.us/benches/network-snapshot-011000.pkl">PKL文件</a>，你也可以下载并使用这个模型。</p><p>3、<a href="https://www.instagram.com/geomorphological_landscapes/">独特的地貌</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/photo-005.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/photo-006.jpg"></p><p>Geomorphological Landscapes 是一个收欢迎的 Ins 帐户，它收集了很多独特的地貌以及组合地形。</p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.ximalaya.com/sound/395044588">神策数据桑文锋: 如何在SaaS创业中打造组织文化？</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/boke-001.jpg"></p><p>桑文锋是 <a href="https://www.sensorsdata.cn/">神策数据</a> 创始人和CEO。神策数据是一家为企业提供大数据分析和营销科技服务的公司，可以简单理解为私有化版本的 Google Analytics 。</p><p>创业前，桑文锋曾在<a href="https://www.baidu.com/">百度</a>任职8年，一手打造了百度用户日志大数据平台。 他拥有浙江大学的本科和硕士学位。 </p><p>节目中，聊了桑文锋从河南农村到浙大的成长经历，也聊了他创业最早期是如何组建团队，如何卖出神策的第一个产品，如何与投资人打交道等。他也分享了自己在组织建设和文化上的思考，包括如何让神策在扩张中保持自己的文化，如何定义组织内部的透明度，以及他从中共党史中学习到的组织建设经验。</p><p>桑文锋推荐了一本<a href="https://baike.baidu.com/item/%E9%87%91%E4%B8%80%E5%8D%97/10894641">金一南</a>写的书 <a href="https://book.douban.com/subject/35177921/">《苦难辉煌》</a>，我目前在读，后续做一个感受分享。</p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://www.infoworld.com/article/3655646/a-brief-history-of-the-agile-methodology.html">敏捷方法的简史</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/post-001.webp"></p><p>今天大多数组织都在实践某种形式的敏捷开发，但是你真的了解什么是敏捷吗？要了解敏捷的成功，有必要回顾一下瀑布方法的全盛时期和敏捷宣言的诞生。</p><p>如果你对敏捷方法还没有基本的认识，那我推荐你阅读 <a href="https://www.infoworld.com/article/3655646/a-brief-history-of-the-agile-methodology.html">敏捷方法的简史</a>，文章详细说明了什么是敏捷、敏捷诞生的背景（瀑布方法强规范下暴露的弊端），以及为什么敏捷开发能提供更好的软件与支持。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-2/post-002.jpg"></p><p>当然，如果暂时没有时间详细阅读，那么你可以先了解《敏捷宣言》的核心思想。</p><p><strong>《敏捷宣言》</strong></p><p>敏捷于 2001 年正式启动，当时 17 位技术人员起草了 <a href="http://agilemanifesto.org/">敏捷软件开发宣言</a>。他们为敏捷项目管理编写了四项主要原则，旨在指导团队开发更好的软件：</p><ul><li>个人和交互 <strong>高于</strong> 流程和工具</li><li>工作软件 <strong>高于</strong> 详尽文档</li><li>客户合作 <strong>高于</strong> 合同谈判</li><li>响应变化 <strong>高于</strong> 遵循计划</li></ul><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、</p><p>当谈到编码（或任何与之相关的东西）时，我全心全意地相信一条规则，这是三十分钟规则。</p><p>规则是，如果有人卡在某件事上超过 30 分钟，他们应该寻求帮助，通过在 30 分钟后寻求帮助，它解决了以下问题：</p><ul><li>我们常常因为我们不知道的事情而陷入困境，无论技能或智力如何，我们都无法继续前进。</li><li>这样我们就不会因为被困在一个问题上太久而感到沮丧。</li><li>有时，简单地提出要问的问题就可以让我们自己回答问题。</li><li>从业务的角度来看，降低成本是因为我们不会在数小时、数天或数周内停滞不前，而是在 30 分钟后继续前进。</li></ul><p>– <a href="https://daniel.feldroy.com/posts/thirty-minute-rule">《三十分钟法则》</a></p><p>2、</p><p>新冠肺炎已经大流行两年多，在全球范围内超过500万人死亡，并且由于各种原因，统计数值肯定是被低估的。肯特州立大学公共卫生学院的流行病学教授 Tara C. Smith 解释了摆脱 COVID-19 的几个难点：</p><ul><li>人畜共患，物品传播</li><li>35% 的感染无症状</li><li>无优质有效疫苗</li><li>不断变异</li><li>政治和经济因素</li></ul><p>现阶段她认为根除的可能性不大，除非能出现突破性的第二代疫苗。</p><p>因此，目前最有效并且可行的办法就是 <strong>消除感染</strong>，在确定的地理区域内减少到零。</p><p>（<em>其实就是我们的动态清零政策，实行一定的地理区域隔离封禁，很好的兼顾到老人孩子等弱势群体，并且保障医疗等公共资源正常有序。未来的政策可能会变,也可能在等待更温和的变异，但是能肯定的是，大家都能得到最大程度的保护。</em>）</p><p>– <a href="https://www.quantamagazine.org/will-we-ever-eradicate-covid-19-20211130/">《我们会摆脱 COVID-19 吗？》</a></p><p>3、</p><p>所谓习惯，就是对外来的刺激做出无意识的反应，或是条件反射式的反应。当身体学会某种行动，不用思考或努力就可以轻松做出反应，这就是习惯。人类有95%的行动是在无意识中进行的，而大部分的无意识行动都是通过习惯产生的。</p><p>可见，习惯是不依赖意志的、无意识的条件反射式的反应。<br>比如刷牙、洗脸，定期洗澡，这些不依赖意志力和毅力就可以自动去做的行为，就是“习惯”。</p><p>习惯不仅限于刷牙、洗脸、洗澡，还包括运动、读书、写作、早睡早起等，只要养成习惯，任何事都可以自然地持续下去而感觉不到丝毫压力。</p><p>– <a href="https://book.douban.com/subject/26771587/">《坚持，一种可以养成的习惯》</a></p><h2 id="订阅"><a href="#订阅" class="headerlink" title="订阅"></a>订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，同步更新在</p><ul><li><a href="https://www.panshenlian.com/weekly">潘深练个人网站</a></li><li>微信公众号：第一次当爸爸吖</li><li> <a href="https://wednesday.zhubai.love/">竹白</a> （支持邮箱订阅、小程序订阅）</li></ul><p>微信搜索“第一次当爸爸吖”或者扫描二维码，即可订阅。</p><p><img src="https://www.panshenlian.com/images/post/wechat/v3.jpg"></p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description></item><item><title>无聊科技正经事周刊（第 1 期）：裁员毕业潮，你焦虑吗？</title><category>科技周刊</category><category>裁员</category><category>焦虑</category><pubDate>Wed, 6 Apr 2022 00:45:17 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/04/06/weekly-1/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>这里记录每周值得分享的科技内容，周三发布。</p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><h2 id="封面图"><a href="#封面图" class="headerlink" title="封面图"></a>封面图</h2><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/cover-001.jpg"></p><p>作品 <strong>《 PEACE  PLEASE 》</strong> 是 <code>Alexey Kulinkovich</code> 最近发布在 Ins 的艺术作品，主题关于乌克兰。（<a href="https://www.behance.net/gallery/139182997/Ukraine-2022">via</a>）</p><h2 id="本周话题：裁员毕业潮，你焦虑吗？"><a href="#本周话题：裁员毕业潮，你焦虑吗？" class="headerlink" title="本周话题：裁员毕业潮，你焦虑吗？"></a>本周话题：裁员毕业潮，你焦虑吗？</h2><p>在互联网圈里，往年的三四月正是被俗称为“金三银四”的跳槽季。但在今年的三月里， 互联网大厂小厂裁员消息不断，按某些公司将裁员称为“毕业”并向被裁员工发“毕业须知”来看，今年的三四月不仅没有成为跳槽季，反而还成了“毕业季”。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/topic-001.png"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/topic-002.png"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/topic-003.jpg"></p><p>一位财经观察人员<a href="https://www.ftchinese.com/story/001095675">闫曼</a>认为，早在2019年以前，互联网红利的见顶就已经意味着市场接近饱和，特别是去年的监管风暴后双减、反垄断，以及疫情影响导致全球经济基本面的变化，所以加速了这一过程。</p><p>然而，不仅仅是互联网，房地产更是行业大裁员，有些稀缺岗位甚至是千人争一岗，从去年的恒大暴雷开始，整个地产行业就一直雷声不断。</p><p>我倒是不制造焦虑恐慌，仅仅认为这段确实艰难，但会在某个时刻触底反弹。</p><p>你现在回过头去看看《走到人生边上》里头的阿菊，还有《苦难辉煌》中的长征，相信你会变得更加乐观。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/topic-004.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/topic-005.jpg"></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/topic-006.jpg"></p><h2 id="科技动态"><a href="#科技动态" class="headerlink" title="科技动态"></a>科技动态</h2><p>1、<a href="https://gizmodo.com/google-new-chrome-os-launcher-has-serious-windows-vibes-1848726234">谷歌推出新的Chrome操作系统启动器——高仿了Windows的开始菜单</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-001.jpg"></p><p>谷歌多年来首次彻底改造 Chrome 操作系统启动器，这也是 <strong>Chromebook</strong> 操作系统的第 100 次更新。</p><p>这个启动器明显就是高仿的 Windows 开始菜单，不过有位网友认为完全不一样，理由是 “ Windows 操作系统的开始菜单是正方形的，而 Chrome 操作系统的启动器是圆角的。”（我哈哈一笑）  </p><p>所以你们有考虑入手一台 Chromebook 吗？</p><p>2、<a href="https://mail.google.com/mail/u/0/#inbox/FMfcgzGmvnvzQzXzNRTkSpdWlpTwsLSG">百万美元像素：深入 NFT 去中心化、版权和铸币</a>（英文，含音频）</p><p>以下这款 NFT 应该是“无聊猴”系列排名第一的作品，售价在 345,000 美元，关注 NFT 的同学应该都见过。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-002.webp"></p><p>目前进入区块链战场的大公司已经数不胜数，具体可以说有两个大领域，分别是 NFT 和 web3，而关于 web3，男神马斯克目前是持怀疑态度，并且前不久认为不太现实？（web3.0 简单释义就是网站内的信息可以直接和其他网站相关信息进行交互）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-003.png"></p><p>但是作为目前区块链领域的两大热门讨论，作为一枚技术人，跟风是肯定要跟的。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-004.png"></p><p>总之，关于 NFT 是如何工作的？ NFT 的去中心化程度如何？这篇文章主要通过使用 IPFS、Alchemy 和 Filebase 的演示来探索 NFT 的去中心化，点击 <a href="https://mail.google.com/mail/u/0/#inbox/FMfcgzGmvnvzQzXzNRTkSpdWlpTwsLSG">去详细阅读</a> 。</p><p>另外，这里有一份简单的<a href="https://www.infoworld.com/article/3653379/a-quick-guide-to-blockchain.html">《区块链快速指南》</a>（英文）。</p><p>3、<a href="https://japantoday.com/category/tech/first-audio-recorded-on-mars-reveals-two-speeds-of-sound">在火星上记录的第一个音频揭示了两种声速</a>（英文）</p><p>去年 2 月，美国宇航局的毅力号火星车登陆火星后，它的两个麦克风开始录音，让科学家们第一次听到火星上的情况。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-005.jpg"></p><p>（图片来源：美国宇航局的恒心漫游者记录了火星表面的第一个声音 | 照片：NASA/JPL-CALTECH/法新社/文件）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-006.jpg"></p><p>在周五发表在《自然》杂志上的一项研究中，科学家们对 Perseverance 的麦克风收集的五个小时的声音进行了首次分析。</p><p>该研究首次证实，火星上的声速较慢，以每秒 240 米的速度传播，而地球上的声速为每秒 340 米。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/keji-007.jpg"></p><p>研究人员根据录音分析出火星上偶尔有阵风，而且得出两种不同的声速——一种用于高音，如激光的撞击声，另一种用于较低频率的声音，如直升机旋翼的呼啸声，这会对听力产生奇怪的延迟效应，例如会让两个距离仅5米的人交流变得特别困难。</p><p>但是，网友认为人类并没有亲身在火星上听过，所以可能有大气层、磁场、矿物质等等因素的影响，导致研究结果只能算是一种推测。</p><h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><p>1、<a href="http://china.zjol.com.cn/202204/t20220405_24039174_ext.shtml">有偿代扫墓、虚拟祭品：当追思成为一门生意</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/article-002.jpg"></p><p>4月3日，根据民政部清明节祭扫工作办公室统计，全国共有2304个网络祭扫平台，网络祭扫群众695万人次，比去年同期增长了275.7%，云祭祀的群众接受度越来越高。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/article-001.jpg"></p><p><strong>云祭祀、代扫墓升温</strong></p><ul><li><p>清明节墓地代扫墓，仅限xx地区，500元包含献花、祭品、上香都可以。代哭、代下跪等私人订制，价钱另议。</p></li><li><p>虚拟祭品种类丰富，分为行礼、供品、火供、祝福四类，包括蜡烛、花圈、食品、服装等，价格从1元至260元不等，用户可购买后在纪念堂献给逝者。如果要给纪念馆点亮长明灯，则需300元至600元一年。</p></li></ul><p>2、<a href="https://mp.weixin.qq.com/s/V_UfsEapfM0Hj2yYFpeaCg">俄乌冲突涉及开源领域事件</a>（中文）</p><p>工信部电子知识产权中心最近根据 “OSC开源社区” 公众号以及互联网相关信息，梳理了目前俄乌冲突涉及开源领域事件。至少涉及：</p><ul><li>Jolla公司，移动操作系统Sailfish OS制造商</li><li>GitHub，世界最大的代码托管平台和开源社区</li><li>甲骨文，全球最大的企业级软件公司</li><li>React，前端三大主流框架之一</li><li>PHP，贡献者众多、影响力巨大的编程语言</li><li>游戏公司Xbox，微软公司旗下的分支机构</li><li>YouTube，全球最大的视频搜索和分享平台</li><li>Node.js生态系统，最大的开源包管理系统</li><li>KDE，最流行的Linux桌面环境之一</li><li>Rust语言，新一代系统及编程语言</li><li>JetBrains，为编程语言提供集成开发环境的明星公司</li><li>GitLab，全球第二大开源代码托管平台</li><li>node-ipc包，vue-cli 的依赖项</li></ul><p>3、<a href="https://thehustle.co/04042022-Asian-languages/?utm_campaign=Amazon%20worker%20union&utm_content=04042022-Asian-languages">亚洲语言正在兴起</a>（英文）</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/article-003.png"></p><p>英语、西班牙语和法语一直处于领先地位。但是最近两年日文和中文上升很快，文章分析日文上升的主要原因是受日本漫画和流行文化的影响，导致大部分年轻人学习日语，而韩语也有上升，主要也是在文化驱动下的受欢迎程度飙升，部分归功于“鱿鱼游戏”等节目。</p><p>关于中文上升的原因，文章中认为原因不太清楚？那作为国人，你认为是什么原因呢？</p><h2 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h2><p>1、<a href="https://www.shell.how/">shell.how</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/tools-001.jpg"></p><p>有网友分享一个网站，理由是：“平时使用 git 各种命令参数根本记不住。每次还要通过百度 / Google 来查询，非常费事儿，这个网站用简单的一句话描述了命令和参数的含义，使用起来非常方便。”</p><p>我试用了一下，该网站存在几个严重的问题：</p><ul><li>缺少联想词提示</li><li>命令库严重不全</li><li>仅一句话描述命令用途，没有help，没有demo</li></ul><p>还是百度和谷歌香！</p><p>2、<a href="https://s2.antv.vision/zh">S2</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/tools-002.png"></p><p>蚂蚁金服antv的S2，一个表格可视化引擎，可以为数据表制作各种各样的效果。</p><p>包括但不仅限于：多维交叉分析领域的表格解决方案，数据驱动视图，提供底层核心库、基础组件库、业务场景库，具备自由扩展的能力，让开发者既能开箱即用，也能基于自身场景自由发挥。</p><h2 id="资源"><a href="#资源" class="headerlink" title="资源"></a>资源</h2><p>1、<a href="https://blog.knownsec.com/Knownsec_RD_Checklist/v2.2.html">知道创宇研发技能表v2.2</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/source-001.png"></p><p>这是知道创宇在 2014 年发布的研发技能表，版本是 V2.2，目前看来，依然有一些值得我们收藏学习。</p><p>2、<a href="https://www.lcevc.org/how-lcevc-works/">LCEVC | 一种新的视频压缩方法</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/source-002.jpg"></p><p>LCEVC（低复杂度增强视频编码）的工作原理是使用任何现有编解码器（基本编解码器）对源图像的较低分辨率版本进行编码，并使用不同的压缩方法（增强）对重建的低分辨率图像和源图像之间的差异进行编码。</p><h2 id="图片"><a href="#图片" class="headerlink" title="图片"></a>图片</h2><p>1、<a href="https://www.hollywoodreporter.com/business/business-news/elon-musk-twitter-stake-stock-1235124807">马斯克购买推特 9.2% 的股份，推动股价飙升</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/photo-001.png"></p><p>马斯克斥资30亿美元购买推特 9.2% 的股份，推动股价飙升并成为最大股东，比 CEO 持股比例还多。</p><p>2、<strong>你们不要再跟着我了！</strong></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/photo-002.jpg"></p><h2 id="播客"><a href="#播客" class="headerlink" title="播客"></a>播客</h2><p>1、<a href="https://www.bilibili.com/video/BV1KL4y1W7EV?spm_id_from=333.999.0.0">【硬核商业分析】Netflix到底赢在哪？</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/boke-01.webp"></p><p>2010年~2020年这十年间，Netflix 在美股大盘中涨幅排名第一。</p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/boke-02.png"></p><p>在流媒体行业，竞争异常激烈、百花齐放，Netflix到底有何高人之处，让他在全球范围内一枝独秀（当然除了大陆，懂的都懂），视频中详细介绍了Netflix的传奇经历。</p><h2 id="文摘"><a href="#文摘" class="headerlink" title="文摘"></a>文摘</h2><p>1、<a href="https://critter.blog/2022/04/04/the-12-week-year-2022-round-2/">计划在40岁前完成的40件事</a></p><p>Mike Crittenden 最近发表了他4月份也就是第12周的计划，其中包括他之前制定的计划 <a href="https://critter.blog/2022/02/28/40-before-40/">《My 40 before 40》</a> 中的6项。</p><p><img src="https://www.panshenlian.com/images/post/live/37/title.jpg" alt="37岁前的37件事"></p><p>对我的震撼很大，平时我基本都是制定年度计划，基本没有想过计划一个5年或10年的计划，因为时间拉长使得我们的目标可以变得更远，很有意思，所以我也罗列了一份<a href="https://www.panshenlian.com/2022/03/02/live-002-my-37-before-37/">《37岁前的37件事》</a>。</p><p>那么问题来了，你的计划呢？</p><h2 id="言论"><a href="#言论" class="headerlink" title="言论"></a>言论</h2><p>1、<br>Progress is impossible without change; and those who cannot change their minds cannot change anything.<br>没有改变，不可能会有进步 </p><p>– <a href="https://xueshu.baidu.com/usercenter/paper/show?paperid=84101325b850cf1b77fdd395e6957b1b">George Bernard Shaw（萧伯纳）</a></p><p><img src="http://weekly.panshenlian.com/_media/images/2022/issue-1/say-001.jpg"></p><p>2、<br>作为一个数字原生代，我的大部分信息都以数字形式存储，这就是我喜欢的方式。但是你拥有的数据越多，组织起来就越重要。</p><p>–<a href="https://dsebastien.net/blog/2022-04-03-25-years-of-personal-knowledge-management">《25年以上的个人知识管理》</a></p><h2 id="订阅"><a href="#订阅" class="headerlink" title="订阅"></a>订阅</h2><p>这里记录每周值得分享的科技内容，周三发布，同步更新在<a href="https://www.panshenlian.com/weekly">潘深练个人网站</a>和微信公众号：第一次当爸爸吖。</p><p>微信搜索“第一次当爸爸吖”或者扫描二维码，即可订阅。</p><p><img src="https://www.panshenlian.com/images/post/wechat/v3.jpg"></p><p>本刊开源（GitHub: <a href="https://github.com/senlypan/weekly">senlypan/weekly</a>），欢迎投稿，推荐或自荐文章/软件/资源，请<a href="https://github.com/senlypan/weekly/issues">提交 issue</a> 。</p><p>（完）</p>]]></content:encoded><description>&lt;p&gt;这里记录每周值得分享的科技内容，周三发布。&lt;/p&gt;
&lt;p&gt;本刊开源（GitHub: &lt;a href="https://github.com/senlypan/weekly"&gt;senlypan/weekly&lt;/a&gt;），欢迎投稿，推荐或自荐文章/软件/资源，请&lt;a href="</description></item><item><title>译文《Java并发编程之并发与并行》</title><category>Java</category><category>译文集</category><category>并发编程</category><category>多线程</category><category>并行</category><pubDate>Mon, 28 Mar 2022 09:08:11 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/03/28/translation-004-concurrency-vs-parallelism/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/title.jpg" alt="translation-004-concurrency-vs-parallelism.md#title.jpg"></p><blockquote><p>作者: 雅各布·詹科夫<br>原文: <a href="http://tutorials.jenkov.com/java-concurrency/concurrency-vs-parallelism.html">http://tutorials.jenkov.com/java-concurrency/concurrency-vs-parallelism.html</a><br>翻译: <a href="https://www.panshenlian.com/">潘深练的个人网站</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~<br>更新: 2022-02-23</p></blockquote><p>并发性和并行性通常用于与多线程程序相关的，最早并发性和并行性似乎指的是相同的概念，但其实并发和并行实际上有不同的含义。在这个并发与并行教程中，我将解释这些概念的含义。</p><p>为了清楚起见，在本文中，我讨论在单个应用程序（单个进程）中的并发性和并行性。不在多个应用程序、进程或计算机之间。</p><h2 id="并发与并行教程视频"><a href="#并发与并行教程视频" class="headerlink" title="并发与并行教程视频"></a>并发与并行教程视频</h2><p>如果您喜欢视频，这里有本教程对应的视频版本: <a href="https://www.youtube.com/watch?v=Y1pgpn2gOSg&list=PLL8woMHwr36EDxjUoCzboZjedsnhLP1j4&index=9">并发与并行教程视频</a></p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/concurrency-vs-parallelism-video-screenshot.png" alt="01-Concurrency-vs-Parallelism#concurrency-vs-parallelism-video-screenshot.png"></p><h2 id="什么是并发"><a href="#什么是并发" class="headerlink" title="什么是并发"></a>什么是并发</h2><p>并发是指在一个应用程序中同时存在多个任务在执行，同时刻或者说看起来是同一时刻（并发）。</p><p>如果计算机只有一个CPU，应用程序可能不会在同一时间完成多个任务，但在应用程序内部一次完成多个任务。要同时在多个任务上取得进展，CPU会在执行期间在不同的任务之间切换。如下图所示：</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/concurrency-vs-parallelism-1.png" alt="01-Concurrency-vs-Parallelism#concurrency-vs-parallelism-1.png"></p><h2 id="什么是并行执行"><a href="#什么是并行执行" class="headerlink" title="什么是并行执行"></a>什么是并行执行</h2><p>并行执行是指计算机具有多个 CPU 或 CPU 内核，并同时在多个任务上取得进展。但是，并行执行并不是指与并行性相同的现象 。稍后我将回到并行性。并行执行如下图所示：</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/concurrency-vs-parallelism-2.png" alt="01-Concurrency-vs-Parallelism#concurrency-vs-parallelism-2.png"></p><h2 id="并行并发执行"><a href="#并行并发执行" class="headerlink" title="并行并发执行"></a>并行并发执行</h2><p>可以进行并行并发执行，其中线程分布在多个 CPU 中。因此，在同一个 CPU 上执行的线程是并发执行的，而在不同 CPU 上执行的线程是并行执行的。下图说明了并行并发执行。</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/concurrency-vs-parallelism-3.png" alt="01-Concurrency-vs-Parallelism#concurrency-vs-parallelism-3.png"></p><h2 id="理解并行性"><a href="#理解并行性" class="headerlink" title="理解并行性"></a>理解并行性</h2><p>并行性意味着一个应用程序将其任务拆分成更小的子任务，这些子任务可以并行处理，例如在多个CPU上同时处理。因此，并行性并不是指与并行执行相同的执行模型，即使它们表面上看起来很相似。</p><p>为了实现真正的并行性，您的应用程序必须运行多个线程，每个线程必须在单独的 CPU/CPU 内核/显卡 GPU 内核或类似内核上运行。</p><p>下图显示了一个更大的任务，它被分为4个子任务。这4个子任务由4个不同的线程执行，它们运行在2个不同的CPU上。这意味着，这些子任务的部分是并行执行的（在同一CPU上执行的），而部分是并行执行的（在不同CPU上执行的）。</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/concurrency-vs-parallelism-4.png" alt="01-Concurrency-vs-Parallelism#concurrency-vs-parallelism-4.png"></p><p>如果这4个子任务由4个线程在各自的CPU上执行（总共4个CPU），那么任务的执行将是完全并行的。然而，要将一个任务分解成与可用CPU数量一样多的子任务并不总是那么容易。通常，将一个任务分解为多个子任务更容易，这些子任务与手头的任务自然匹配，然后让线程调度器负责在可用CPU之间分配线程。</p><h2 id="并发和并行组合"><a href="#并发和并行组合" class="headerlink" title="并发和并行组合"></a>并发和并行组合</h2><p>综上所述，并发性指的是多个任务在单个CPU上看似同时取得进展。</p><p>另一方面，并行性与应用程序如何并行执行单个任务有关，通常是通过将任务拆分为可以并行完成的子任务。</p><p>这两种执行方式可以在同一个应用程序中组合。我将在下面介绍其中一些组合。</p><h3 id="并发，非并行"><a href="#并发，非并行" class="headerlink" title="并发，非并行"></a>并发，非并行</h3><p>应用程序可以是并发的，但不能是并行的。这意味着它似乎同时（同时）在多个任务上取得进展，但应用程序会在每个任务上取得进展之间切换，直到任务完成。在并行线程/CPU中没有真正的任务并行执行。</p><h3 id="并行，不并发"><a href="#并行，不并发" class="headerlink" title="并行，不并发"></a>并行，不并发</h3><p>应用程序也可以是并行的，但不能是并发的。这意味着应用程序一次只能处理一个任务，而这个任务被分解成可以并行处理的子任务。但是，每个任务（+子任务）都是在下一个任务被拆分并并行执行之前完成的。</p><h3 id="既不并发也不并行"><a href="#既不并发也不并行" class="headerlink" title="既不并发也不并行"></a>既不并发也不并行</h3><p>此外，应用程序既不能是并发的，也不能是并行的。这意味着它一次只能处理一个任务，而且任务永远不会分解为并行执行的子任务。小型命令行应用程序可能就是这种情况，因为它只有一个作业，太小了，无法并行化。</p><h3 id="并发且并行"><a href="#并发且并行" class="headerlink" title="并发且并行"></a>并发且并行</h3><p>最后，应用程序还可以通过两种方式同时并发和并行：</p><p>第一种是简单的并行执行。如果应用程序启动多个线程，然后在多个CPU上执行，就会发生这种情况。</p><p>第二种方式是应用程序同时处理多个任务，并将每个任务分解为子任务，同时以并行的方式执行。但是在这种情况下，并发和并行的一些性能优势可能会丢失，因为计算机中的 CPU 基于在频繁于并发或并行处理。所以并发且并行，可能只会带来微小的性能提升甚至可能是性能损失。因此，除非有特殊目的并且已经提前进行了充分分析和测量，否则不建议采用并发并行模型。</p><p>（本篇完）</p><blockquote><p>作者: 雅各布·詹科夫<br>原文: <a href="http://tutorials.jenkov.com/java-concurrency/concurrency-vs-parallelism.html">http://tutorials.jenkov.com/java-concurrency/concurrency-vs-parallelism.html</a><br>翻译: <a href="https://www.panshenlian.com/">潘深练的个人网站</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~<br>更新: 2022-02-23</p></blockquote>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/java/concurrency/concurrency-vs-parallelism/title.jpg" alt="translation-004-concurrency</description></item><item><title>译文《Java并发编程之volatile》</title><category>Java</category><category>译文集</category><category>并发编程</category><category>volatile</category><pubDate>Tue, 22 Mar 2022 09:18:05 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/03/22/translation-003-java-volatile-keyword/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="/images/post/java/concurrency/volatile/title.jpg" alt="java-volatile-keyword.md#title.jpg"></p><blockquote><p>作者: 雅各布·詹科夫<br>原文: <a href="http://tutorials.jenkov.com/java-concurrency/volatile.html">http://tutorials.jenkov.com/java-concurrency/volatile.html</a><br>翻译: <a href="https://www.panshenlian.com/">潘深练</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~<br>更新: 2022-02-24</p></blockquote><p>Java的<code>volatile</code>关键字用于将Java变量标记为“存储在主内存中”。更准确地说，每次对<code>volatile</code>变量的读取都将从计算机主内存中读取，而不是从CPU缓存中读取，并且每次对<code>volatile</code>变量的写入都将写入主内存，而不仅仅写在CPU缓存。</p><p>事实上，自从 Java5 开始，<code>volatile</code> 关键字就不仅仅被用来保证 <code>volatile</code> 变量读写主内存。我将在以下内容解释这一点。</p><h2 id="Java-volatile-教程视频"><a href="#Java-volatile-教程视频" class="headerlink" title="Java volatile 教程视频"></a>Java volatile 教程视频</h2><p>如果你喜欢视频，我在这里有这个 <code>Java volatile</code> 教程的视频版本:<br><a href="https://www.youtube.com/watch?v=nhYIEqt-jvY">Java volatile 教程视频</a></p><p><img src="/images/post/java/concurrency/volatile/java-volatile-video-screenshot.jpg" alt="java-volatile-keyword.md#java-volatile-video-screenshot.jpg"></p><h2 id="变量可见性问题"><a href="#变量可见性问题" class="headerlink" title="变量可见性问题"></a>变量可见性问题</h2><p>Java的<code>volatile</code>关键字在多线程处理中保证了共享变量的“可见性”。这听起来可能有点抽象，所以让我详细说明。</p><p>在多线程应用程序中，如果多个线程对同一个无声明<code>volatile</code>关键词的变量进行操作，出于性能原因，每个线程可以在处理变量时将变量从主内存复制到CPU缓存中。如果你的计算机拥有多CPU，则每个线程可能在不同的CPU上运行。这就意味着，每个线程都可以将变量复制在不同CPU的CPU缓存上。这在此处进行了说明：</p><p><img src="/images/post/java/concurrency/volatile/java-volatile-1.png" alt="java-volatile-keyword.md#java-volatile-1.png"></p><p>对于无声明<code>volatile</code>关键词的变量而言，无法保证Java虚拟机（JVM）何时将数据从主内存读取到CPU缓存，或者将数据从CPU缓存写入主内存。这就可能会导致几个问题，我将在以下部分内容解释这些问题。</p><p>想象一个场景，多个线程访问一个共享对象，该对象包含一个声明如下的计数器（counter）变量：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SharedObject</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="variable">counter</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>假设只有线程1会增加计数器（counter）变量的值，但是线程1和线程2会不时的读取这个计数器变量。</p><p>如果计数器（counter）变量没有声明<code>volatile</code>关键词，则无法保证计数器变量的值何时从CPU缓存写回主内存。这就意味着，每个CPU缓存上的计数器变量值和主内存中的变量值可能不一致。这种情况如下所示：</p><p><img src="/images/post/java/concurrency/volatile/java-volatile-2.png" alt="java-volatile-keyword.md#java-volatile-2.png"></p><p>一个线程的写操作还没有写回主内存（每个线程都有本地缓存，即CPU缓存，一般写入成功会从cpu缓存刷新至主内存），其他线程看不到变量的最新值，这就是“可见性”问题，即一个线程的更新对其他线程是不可见的。</p><h2 id="Java-volatile-可见性保证"><a href="#Java-volatile-可见性保证" class="headerlink" title="Java volatile 可见性保证"></a>Java volatile 可见性保证</h2><p>Java的<code>volatile</code>关键字就是为了解决变量的可见性问题。通过对计数器（counter）变量声明<code>volatile</code>关键字，所有线程对该变量的写入都会被立即同步到主内存中，并且，所有线程对该变量的读取都会直接从主内存读取。</p><p>以下是计数器（counter）变量声明了关键字<code>volatile</code>的用法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SharedObject</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">volatile</span> <span class="type">int</span> <span class="variable">counter</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>因此，声明了<code>volatile</code>关键字的变量，保证了其他线程对该变量的写入可见性。</p><p>在以上给出的场景中，一个线程（T1）修改了计数器变量，而另一个线程（T2）读取计数器变量（但是没有进行修改），这种场景下如果给计数器（counter）变量声明<code>volatile</code>关键字，就能够保证计数器（counter）变量的写入对线程（T2）是可见的。</p><p>但是如果线程（T1）和线程（T2）都对计数器（counter）变量进行了修改，那么给计数器（counter）变量声明<code>volatile</code>关键字是无法保证可见性的，稍后讨论。</p><h3 id="volatile-全局可见性保证"><a href="#volatile-全局可见性保证" class="headerlink" title="volatile 全局可见性保证"></a>volatile 全局可见性保证</h3><p>实际上，Java的<code>volatile</code>关键字可见性保证超过了<code>volatile</code>变量本身的可见性，可见性保证如下：</p><ul><li><p>如果线程A写入一个<code>volatile</code>变量，而线程B随后读取了同一个<code>volatile</code>变量，那么所有变量的可见性，在线程A写入<code>volatile</code>变量之前对线程A可见，在线程B读取<code>volatile</code>变量之后对线程B同样可见。</p></li><li><p>如果线程A读取一个<code>volatile</code>变量，那么读取<code>volatile</code>变量时，对线程A可见的所有变量也会从主内存中重新读取。</p></li></ul><p>让我用一个代码示例来说明:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> years;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> months</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> days;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(<span class="type">int</span> years, <span class="type">int</span> months, <span class="type">int</span> days)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.years  = years;</span><br><span class="line">        <span class="built_in">this</span>.months = months;</span><br><span class="line">        <span class="built_in">this</span>.days   = days;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>udpate()</code>方法写入三个变量，其中只有变量days声明为<code>volatile</code>。</p><blockquote><p><code>volatile</code>关键字声明的变量，被写入时会直接从本地线程缓存刷新到主内存。</p></blockquote><p><code>volatile</code>的全局可见性保证，指的是当一个值被写入<code>days</code>时，所有对当前写入线程可见的变量也都会被写入到主内存。意思就是当一个值被写入<code>days</code>变量时，<code>year</code>变量和<code>months</code>变量也会被写入到主内存。</p><p>在读<code>years</code>，<code>months</code>和<code>days</code>的值时，你可以这样做：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> years;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> months</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> days;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">int</span> <span class="title function_">totalDays</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">total</span> <span class="operator">=</span> <span class="built_in">this</span>.days;</span><br><span class="line">        total += months * <span class="number">30</span>;</span><br><span class="line">        total += years * <span class="number">365</span>;</span><br><span class="line">        <span class="keyword">return</span> total;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(<span class="type">int</span> years, <span class="type">int</span> months, <span class="type">int</span> days)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.years  = years;</span><br><span class="line">        <span class="built_in">this</span>.months = months;</span><br><span class="line">        <span class="built_in">this</span>.days   = days;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意，<code>totalDays()</code>方法会首先读取<code>days</code>变量的值到total变量中，当程序读取<code>days</code>变量时，也会从主内存读取<code>month</code>变量和<code>years</code>变量的值。因此你可以通过以上的读取顺序，来保证读取到三个变量<code>days</code>,<code>months</code>和<code>years</code>最新的值。</p><h2 id="指令重排序的挑战"><a href="#指令重排序的挑战" class="headerlink" title="指令重排序的挑战"></a>指令重排序的挑战</h2><p>为了提高性能，一般允许 JVM 和 CPU 在保证程序语义不变的情况下对程序中的指令进行重新排序。例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">a++;</span><br><span class="line">b++;</span><br></pre></td></tr></table></figure><p>这些指令可以重新排序为以下顺序，而不会丢失程序的语义含义：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span>;</span><br><span class="line">a++;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span>;</span><br><span class="line">b++;</span><br></pre></td></tr></table></figure><p>然而，当其中一个变量是<code>volatile</code>关键字声明的变量时，指令重排就会遇到一些挑战。让我们看看之前教程中的<code>MyClass</code>类示例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass</span> &#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> years;</span><br><span class="line">    <span class="keyword">private</span> <span class="type">int</span> months</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">int</span> days;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(<span class="type">int</span> years, <span class="type">int</span> months, <span class="type">int</span> days)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.years  = years;</span><br><span class="line">        <span class="built_in">this</span>.months = months;</span><br><span class="line">        <span class="built_in">this</span>.days   = days;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>一旦<code>update()</code>方法将一个值写入days变量，那么写入years变量和months变量的最新值也会被写入到主内存当中。但是，如果Java虚拟机对指令进行重排，例如这样：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">update</span><span class="params">(<span class="type">int</span> years, <span class="type">int</span> months, <span class="type">int</span> days)</span>&#123;</span><br><span class="line">    <span class="built_in">this</span>.days   = days;</span><br><span class="line">    <span class="built_in">this</span>.months = months;</span><br><span class="line">    <span class="built_in">this</span>.years  = years;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当修改<code>days</code>变量时，仍然会将<code>months</code>变量和<code>years</code>变量的值写入主内存，但是这个节点是发生在新值写入<code>months</code>变量和<code>years</code>变量之前。因此<code>months</code>变量和<code>years</code>变量的最新值不可能正确地对其他线程可见。这种重排指令会导致语义发生改变。</p><p>针对这个问题Java提供了一个解决方案，我们往下看。</p><h2 id="Java-volatile-Happens-Before-规则"><a href="#Java-volatile-Happens-Before-规则" class="headerlink" title="Java volatile Happens-Before 规则"></a>Java volatile Happens-Before 规则</h2><p>为了解决指令重新排序的挑战，除了可见性保证之外，Java的<code>volatile</code>关键字还提供了Happens-Before规则。Happens-Before规则保证：</p><ul><li>如果其他变量的读写操作原先就发生在<code>volatile</code>变量的写操作之前，那么其他变量的读写指令不能被重排序到volatile变量的写指令之后;<ul><li>在<code>volatile</code>变量写入之前，发生的其他变量的读写，Happens-Before 于<code>volatile</code>变量的写入。</li></ul></li></ul><blockquote><p>注意：例如在<code>volatile</code>变量写入之后的其他变量读写，仍然可能被重排到<code>volatile</code>变量写入之前。只不过不能反着来，允许后面的读写重排到前面，但不允许前面的读写重排到后面。</p></blockquote><ul><li>如果其他变量的读写操作原先就发生在<code>volatile</code>变量读操作之后，那么其他变量的读写指令不能被重排序到volatile变量的读指令之前; </li></ul><blockquote><p>注意：例如在<code>volatile</code>变量读之前的其他变量读取，可能被重排到<code>volatile</code>变量的读之后。只不过不能反着来，允许前面的读取重排到后面，但不允许后面的读取重排到前面。</p></blockquote><p>上述的Happens-Before规则，确保了<code>volatile</code>关键字的可见性保证会被强制要求。</p><h2 id="仅声明-volatile-不足以保证线程安全"><a href="#仅声明-volatile-不足以保证线程安全" class="headerlink" title="仅声明 volatile 不足以保证线程安全"></a>仅声明 volatile 不足以保证线程安全</h2><p>即使<code>volatile</code>关键字保证直接从主内存读取<code>volatile</code>变量，并且所有对<code>volatile</code>变量的写入都直接写入主内存，在某些情况下仅仅声明变量<code>volatile</code>是不足以保证线程安全的。</p><p>在前面解释的情况中，只有线程1写入共享计数器变量，声明计数器变量volatile足以确保线程2始终看到最新的写入值。</p><p>事实上，如果写入变量的新值不需要依赖之前的值，那多个线程可以同时对一个<code>volatile</code>共享变量进行写入操作，并且在主内存中仍然存储正确的值。换而言之，如果一个线程仅对一个<code>volatile</code>共享变量进行写入操作，那并不需要先读取出这个变量的值，再通过计算得到下一个值。</p><p>一旦线程需要首先读取出<code>volatile</code>变量的值，再基于该值为<code>volatile</code>共享变量生成新值，那<code>volatile</code>变量就不再足以保证正确的可见性。在读取<code>volatile</code>变量和写入新值之间的短暂时间会产生资源竞争，存在多个线程同时来读取<code>volatile</code>变量并得到相同的值，且都为变量赋予新值，然后将值都写回主内存中，从而会覆盖掉彼此的值。</p><p>多个线程递增同个计数器（counter）变量的情况，导致<code>volatile</code>变量不够保证线程安全性。 以下部分更详细地解释了这种情况:</p><p>想象一下，如果线程1将值为0的共享计数器（counter）变量读入其CPU高速缓存，则将其递增为1并且还未将更改的值写回主内存。 同时间线程2也可以从主内存中读取到相同的计数器变量，其中变量的值仍为0，存进其自己的CPU高速缓存。 然后，线程2也可以将计数器（counter）递增到1，也还未将其写回主内存。 这种情况如下图所示：</p><p><img src="/images/post/java/concurrency/volatile/java-volatile-3.png" alt="02-Java Volatile Keyword#java-volatile-3.png"></p><p>线程1和线程2现在几乎不同步。共享计数器（counter）变量的实际值应该是2，但每个线程在其CPU缓存中的变量值为1，在主内存中该值仍然为0。真是一团糟！即使线程最终将其共享计数器变量的值写回主内存，该值也将是错误的。</p><h2 id="volatile-何时是线程安全的"><a href="#volatile-何时是线程安全的" class="headerlink" title="volatile 何时是线程安全的"></a>volatile 何时是线程安全的</h2><p>正如我前面提到的，如果两个线程都在读取和写入共享变量，那么使用<code>volatile</code>关键字是不足以保证线程安全的。一般这种情况下，您需要使用<code>synchronized</code>来保证变量的读取和写入是原子性的。读取或写入<code>volatile</code>变量不会阻塞其他线程读取或写入。为此，您必须在关键部分周围使用<code>synchronized</code>关键字。</p><p>作为<code>synchronized</code>块的替代方案，您可以选择使用<code>java.util.concurrent</code>并发包中的原子数据类型。 例如，<code>AtomicLong</code>或<code>AtomicReference</code>或其它之一。</p><p>如果只有一个线程读取和写入<code>volatile</code>变量的值，而其他线程只读取变量，那么读取线程将保证看到写入<code>volatile</code>变量的最新值。 如果不使变量变为<code>volatile</code>，则无法保证。</p><p><code>volatile</code>关键字保证适用于32位和64位。</p><h2 id="volatile-的性能注意事项"><a href="#volatile-的性能注意事项" class="headerlink" title="volatile 的性能注意事项"></a>volatile 的性能注意事项</h2><p>读写<code>volatile</code>变量都会直接从主内存读写，比从CPU缓存读写要花更多的开销，但访问<code>volatile</code>变量可以阻止指令重排，这是一项正常的性能增强技术。因此，除非确实需要强制实施变量的可见性，否则其他情况减少使用<code>volatile</code>变量。</p><p>（本篇完）</p><blockquote><p>✨ 译文来源：<a href="https://www.panshenlian.com/">潘深练</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~</p></blockquote>]]></content:encoded><description>&lt;p&gt;&lt;img src="/images/post/java/concurrency/volatile/title.jpg" alt="java-volatile-keyword.md#title.jpg"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作者: 雅各布·詹科夫&lt;br&gt;</description></item><item><title>一文读懂Java动态代理</title><category>Java</category><category>动态代理</category><category>AOP</category><category>一文读懂</category><pubDate>Tue, 15 Mar 2022 01:55:15 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/03/15/java-001-java-dynamic-proxy/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>事实上，对于很多Java编程人员来说，可能只需要达到从入门到上手的编程水准，就能很好的完成大部分研发工作。除非自己强主动获取，或者工作倒逼你学习，否则我们好像没必要去真正了解Java编程，或者深入研究JDK运行原理、或者在实际工作中某个模块写一套设计模式、或者纠结一个线程安全问题。</p><p>我觉得完全没必要了解，因为很多知识内容，我技术储备上仅仅点到为止，就能胜任工作，何必深入？确实，我也和有些朋友一样，8年编程生涯以来大部分时候都存在这种思想，直到某一天突然有机会来了，你就要负责某个系统的架构规划设计、你就要全权保障整个企业的信息化安全、你就要管理底下几十上百号研发兄弟，但你知道你只擅长if/else，你清楚的知道你写过一个下单方法有9000行代码，你刚刚接到一个运维同事的电话，说你某个SQL执行后CPU飙到100%…</p><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-001.png" alt="02-java-dynamic-proxy-001.png"></p><p>对，这就是你我现在或未来，都终将会面临同时躲不过的问题，所以我想寻思着要不就来一个 <strong>「一文读懂」</strong> 系列，我们深入浅出，跟大家一块研究学习，从Java基础到核心，从单体框架到微服务集群，从框架到架构，我们都一起分享，同步成长，Java之路，应不忘初心，终生学习。</p><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-002.png" alt="02-java-dynamic-proxy-002.png"></p><h2 id="引言"><a href="#引言" class="headerlink" title="引言"></a>引言</h2><p>最早的代理模式，我们大致可以联想到三国时期，孟德君挟天子以令诸侯是代理模式，是权利代理；现今生活中类似房产中介、票务中介是代理模式，是业务代理；还有翻墙浏览网页是代理模式，是VPN代理；回到我们编程世界里呢，以前你用的远程方法调用（RMI）是代理、企业JavaBeans（EJB）是代理，现在流行的众多RPC框架（如dubbo）也是代理，包括我们的Java动态代理，他们都是对象代理。</p><h2 id="Java动态代理"><a href="#Java动态代理" class="headerlink" title="Java动态代理"></a>Java动态代理</h2><p>Java 动态代理机制的出现，使得 Java 开发人员不用手工编写代理类，只要简单地指定一组接口及委托类对象，便能动态地获得代理类。代理类会负责将所有的方法调用分派到委托对象上<strong>反射</strong>执行，在分派执行的过程中，开发人员还可以按需调整委托类对象及其功能，这是一套非常灵活有弹性的代理框架。</p><blockquote><p>代理是一种常见的设计模式，其目的就是为 ”调用方“ 提供一个代理类以控制对 ”被调用方“ 的访问。代理类负责为委托类预处理消息，过滤消息并转发消息，以及进行消息被委托类执行后的后续处理。</p></blockquote><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-003.png" alt="02-java-dynamic-proxy-003.png"></p><p>可以发现，代理类与委托类，实现了同一个接口，所以对于客户端请求来说没有丝毫的区别，这也是Java面向接口编程的特点。代理模式使用代理对象（代理类）完成用户请求，有效的屏蔽了用户对真实对象（委托类）的访问，也可以很好地隐藏和保护委托类对象。同时也为添加不同控制策略争取了空间，从而在设计上获得了更大的灵活性。Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。</p><p>正如现实世界的代理人被授权执行当事人的一些事宜，无需当事人出面，从第三方的角度看，似乎当事人并不存在，因为他只和代理人通信。而事实上代理人是要有当事人的授权，并且在核心问题上还需要请示当事人，同时代理人除了转发完成当事人的授权事宜之外，还可以在执行授权事宜前后增加额外的一些事务。</p><blockquote><p>代理对象 = 目标对象 + 增强事务</p></blockquote><h2 id="相关类和接口"><a href="#相关类和接口" class="headerlink" title="相关类和接口"></a>相关类和接口</h2><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-004.png" alt="02-java-dynamic-proxy-004.png"></p><p>要了解 Java 动态代理的机制，首先需要了解两个相关的类或接口：</p><ul><li>java.lang.reflect.Proxy：这是 Java 动态代理机制的主类，它提供了一组静态方法来为一组接口动态地生成代理类及其对象。</li></ul><h3 id="清单-1-Proxy-的静态方法"><a href="#清单-1-Proxy-的静态方法" class="headerlink" title="清单 1. Proxy 的静态方法"></a>清单 1. Proxy 的静态方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器</span></span><br><span class="line"><span class="keyword">static</span> InvocationHandler </span><br><span class="line">    <span class="title function_">getInvocationHandler</span><span class="params">(Object proxy)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法 2：该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象</span></span><br><span class="line"><span class="keyword">static</span> Class <span class="title function_">getProxyClass</span><span class="params">(</span></span><br><span class="line"><span class="params">    ClassLoader loader, </span></span><br><span class="line"><span class="params">    Class[] interfaces)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法 3：该方法用于判断指定类对象是否是一个动态代理类</span></span><br><span class="line"><span class="keyword">static</span> <span class="type">boolean</span> <span class="title function_">isProxyClass</span><span class="params">(Class cl)</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法 4：该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例</span></span><br><span class="line"><span class="keyword">static</span> Object <span class="title function_">newProxyInstance</span><span class="params">(</span></span><br><span class="line"><span class="params">    ClassLoader loader, </span></span><br><span class="line"><span class="params">    Class[] interfaces,</span></span><br><span class="line"><span class="params">    InvocationHandler h)</span></span><br><span class="line">    </span><br></pre></td></tr></table></figure><ul><li>java.lang.reflect.InvocationHandler：这是调用处理器接口，它自定义了一个 invoke 方法，用于集中处理在动态代理类对象上的方法调用，通常在该方法中实现对委托类的代理访问。</li></ul><h3 id="清单-2-InvocationHandler-的核心方法"><a href="#清单-2-InvocationHandler-的核心方法" class="headerlink" title="清单 2. InvocationHandler 的核心方法"></a>清单 2. InvocationHandler 的核心方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 该方法负责集中处理动态代理类上的所有方法调用。</span></span><br><span class="line"><span class="comment">// 第一个参数既是代理类实例，</span></span><br><span class="line"><span class="comment">// 第二个参数是被调用的方法对象</span></span><br><span class="line"><span class="comment">// 第三个方法是调用参数。</span></span><br><span class="line"><span class="comment">// 调用处理器根据这三个参数</span></span><br><span class="line"><span class="comment">// 进行预处理或分派到委托类实例上反射执行</span></span><br><span class="line">Object <span class="title function_">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span></span><br><span class="line">    </span><br></pre></td></tr></table></figure><p>每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象（参见 Proxy 静态方法 4 的第三个参数）。</p><ul><li>java.lang.ClassLoader：这是类装载器类，负责将类的字节码装载到 Java 虚拟机（JVM）中并为其定义类对象，然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用，它与普通类的唯一区别就是其字节码是由 JVM 在 <strong>运行时动态生成</strong> 的而非预存在于任何一个 .class 文件中。</li></ul><p>每次生成动态代理类对象时都需要指定一个类装载器对象（参见 Proxy 静态方法 4 的第一个参数）</p><h2 id="代理机制与特点"><a href="#代理机制与特点" class="headerlink" title="代理机制与特点"></a>代理机制与特点</h2><p>首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤：</p><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-005.png" alt="02-java-dynamic-proxy-005.png"></p><ol><li>通过实现 InvocationHandler 接口创建自己的调用处理器；</li><li>通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类；</li><li>通过反射机制获得动态代理类的构造函数，其唯一参数类型是调用处理器接口类型；</li><li>通过构造函数创建动态代理类实例，构造时调用处理器对象作为参数被传入。</li></ol><h3 id="清单-3-InvocationHandler-的核心方法"><a href="#清单-3-InvocationHandler-的核心方法" class="headerlink" title="清单 3. InvocationHandler 的核心方法"></a>清单 3. InvocationHandler 的核心方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// InvocationHandlerImpl 实现了 InvocationHandler 接口，</span></span><br><span class="line"><span class="comment">// 并能实现方法调用从代理类到委托类的分派转发</span></span><br><span class="line"><span class="comment">// 其内部通常包含指向委托类实例的引用，</span></span><br><span class="line"><span class="comment">// 用于真正执行分派转发过来的方法调用</span></span><br><span class="line"><span class="type">InvocationHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InvocationHandlerImpl</span>(..);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过 Proxy 为包括 Interface 接口在内的一组接口</span></span><br><span class="line"><span class="comment">// 动态创建代理类的类对象</span></span><br><span class="line"><span class="type">Class</span> <span class="variable">clazz</span> <span class="operator">=</span> Proxy.getProxyClass(</span><br><span class="line">    classLoader, </span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123; Interface.class, ... &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过反射从生成的类对象获得构造函数对象</span></span><br><span class="line"><span class="type">Constructor</span> <span class="variable">constructor</span> <span class="operator">=</span> clazz.getConstructor(</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123; InvocationHandler.class &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过构造函数对象创建动态代理类实例</span></span><br><span class="line"><span class="type">Interface</span> <span class="variable">Proxy</span> <span class="operator">=</span> (Interface)constructor.newInstance(</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Object</span>[] &#123; handler &#125;);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>实际使用过程更加简单，因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程，所以简化后的过程如下</p><h3 id="清单-4-简化的动态代理对象创建过程（三合一）"><a href="#清单-4-简化的动态代理对象创建过程（三合一）" class="headerlink" title="清单 4. 简化的动态代理对象创建过程（三合一）"></a>清单 4. 简化的动态代理对象创建过程（三合一）</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// InvocationHandlerImpl 实现了 InvocationHandler 接口，</span></span><br><span class="line"><span class="comment">// 并能实现方法调用从代理类到委托类的分派转发</span></span><br><span class="line"><span class="type">InvocationHandler</span> <span class="variable">handler</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InvocationHandlerImpl</span>(..);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 通过 Proxy 直接创建动态代理类实例</span></span><br><span class="line"><span class="type">Interface</span> <span class="variable">proxy</span> <span class="operator">=</span> (Interface)Proxy.newProxyInstance( </span><br><span class="line">     classLoader,</span><br><span class="line">     <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123; Interface.class &#125;,</span><br><span class="line">     handler );</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>接下来让我们来了解一下 Java 动态代理机制的一些特点。</p><p>首先是动态生成的代理类本身的一些特点。</p><ul><li>包：如果所代理的接口都是 public 的，那么它将被定义在顶层包（即包路径为空），如果所代理的接口中有非 public 的接口（因为接口不能被定义为 protect 或 private，所以除 public 之外就是默认的 package 访问级别），那么它将被定义在该接口所在包（假设代理了 com.panshenlian.proxy 包中的某非 public 接口 A，那么新生成的代理类所在的包就是 com.panshenlian.proxy ），这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问；</li><li>类修饰符：该代理类具有 final 和 public 修饰符，意味着它可以被所有的类访问，但是不能被再度继承；</li><li>类名：格式是”$ProxyN”，其中 N 是一个逐一递增的阿拉伯数字，代表 Proxy 类第 N 次生成的动态代理类，值得注意的一点是，并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加，原因是如果对同一组接口（包括接口排列的顺序相同）试图重复创建动态代理类，它会很聪明地返回先前已经创建好的代理类的类对象，而不会再尝试去创建一个全新的代理类，这样可以节省不必要的代码重复生成，提高了代理类的创建效率。</li><li>类继承关系：该类的继承关系如图：</li></ul><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-006.png" alt="02-java-dynamic-proxy-006.png"></p><blockquote><p> 由图可见，Proxy 类是它的父类，这个规则适用于所有由 Proxy 创建的动态代理类。而且该类还实现了其所代理的一组接口，这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因。</p></blockquote><p>接下来让我们了解一下代理类实例的一些特点。每个实例都会关联一个调用处理器对象，可以通过 Proxy 提供的静态方法 getInvocationHandler 去获得代理类实例的调用处理器对象。在代理类实例上调用其代理的接口中所声明的方法时，这些方法最终都会由调用处理器的 invoke 方法执行，此外，值得注意的是，代理类的根类 java.lang.Object 中有三个方法也同样会被分派到调用处理器的 invoke 方法执行，它们是 hashCode，equals 和 toString，可能的原因有：</p><ul><li>一是因为这些方法为 public 且非 final 类型，能够被代理类覆盖；</li><li>二是因为这些方法往往呈现出一个类的某种特征属性，具有一定的区分度，所以为了保证代理类与委托类对外的一致性，这三个方法也应该被分派到委托类执行。当代理的一组接口有重复声明的方法且该方法被调用时，代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器，而无论代理类实例是否正在以该接口（或继承于该接口的某子接口）的形式被外部引用，因为在代理类内部无法区分其当前的被引用类型。</li></ul><h3 id="接着来了解一下被代理的一组接口有哪些特点。"><a href="#接着来了解一下被代理的一组接口有哪些特点。" class="headerlink" title="接着来了解一下被代理的一组接口有哪些特点。"></a>接着来了解一下被代理的一组接口有哪些特点。</h3><p>首先，要注意不能有重复的接口，以避免动态代理类代码生成时的编译错误。其次，这些接口对于类装载器必须可见，否则类装载器将无法链接它们，将会导致类定义失败。再次，需被代理的所有非 public 的接口必须在同一个包中，否则代理类生成也会失败。最后，接口的数目不能超过 65535，这是 JVM 设定的限制。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Proxy.java</span></span><br><span class="line"><span class="comment"> * Generate a proxy class.  Must call the checkProxyAccess method</span></span><br><span class="line"><span class="comment"> * to perform permission checks before calling this.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> Class&lt;?&gt; getProxyClass0(</span><br><span class="line">          ClassLoader loader,Class&lt;?&gt;... interfaces) &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (interfaces.length &gt; <span class="number">65535</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(<span class="string">&quot;interface limit exceeded&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// If the proxy class defined by the given loader implementing</span></span><br><span class="line">    <span class="comment">// the given interfaces exists, this will simply return the cached copy;</span></span><br><span class="line">    <span class="comment">// otherwise, it will create the proxy class via the ProxyClassFactory</span></span><br><span class="line">    <span class="keyword">return</span> proxyClassCache.get(loader, interfaces);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>最后再来了解一下异常处理方面的特点。从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常，因为所有的异常都继承于 Throwable 接口，但事实是否如此呢？答案是否定的，原因是我们必须遵守一个继承原则：即子类覆盖父类或实现父接口的方法时，抛出的异常必须在原方法支持的异常列表之内。所以虽然调用处理器理论上讲能够，但实际上往往受限制，除非父接口中的方法支持抛 Throwable 异常。那么如果在 invoke 方法中的确产生了接口方法声明中不支持的异常，那将如何呢？放心，Java 动态代理类已经为我们设计好了解决方法：它将会抛出 UndeclaredThrowableException 异常。这个异常是一个 RuntimeException 类型，所以不会引起编译错误。通过该异常的 getCause 方法，还可以获得原来那个不受支持的异常对象，以便于错误诊断。</p><h2 id="代码演示"><a href="#代码演示" class="headerlink" title="代码演示"></a>代码演示</h2><p>机制和特点都介绍过了，接下来让我们通过源代码来了解一下 Proxy 到底是如何实现的。</p><p>首先记住 Proxy 的几个重要的静态变量：</p><h3 id="清单-5-Proxy-的重要静态变量"><a href="#清单-5-Proxy-的重要静态变量" class="headerlink" title="清单 5. Proxy 的重要静态变量"></a>清单 5. Proxy 的重要静态变量</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 映射表：用于维护类装载器对象到其对应的代理类缓存</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Map</span> <span class="variable">loaderToCache</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">WeakHashMap</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 标记：用于标记一个动态代理类正在被创建中</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Object</span> <span class="variable">pendingGenerationMarker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 同步表：记录已经被创建的动态代理类类型，主要被方法 isProxyClass 进行相关的判断</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Map</span> <span class="variable">proxyClasses</span> <span class="operator">=</span> Collections.synchronizedMap(<span class="keyword">new</span> <span class="title class_">WeakHashMap</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 关联的调用处理器引用</span></span><br><span class="line"><span class="keyword">protected</span> InvocationHandler h;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>然后，来看一下 Proxy 的构造方法：</p><h3 id="清单-6-Proxy-构造方法"><a href="#清单-6-Proxy-构造方法" class="headerlink" title="清单 6. Proxy 构造方法"></a>清单 6. Proxy 构造方法</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 由于 Proxy 内部从不直接调用构造函数，</span></span><br><span class="line"><span class="comment">// 所以 private 类型意味着禁止任何调用</span></span><br><span class="line"><span class="keyword">private</span> <span class="title function_">Proxy</span><span class="params">()</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 由于 Proxy 内部从不直接调用构造函数，</span></span><br><span class="line"><span class="comment">// 所以 protected 意味着只有子类可以调用</span></span><br><span class="line"><span class="keyword">protected</span> <span class="title function_">Proxy</span><span class="params">(InvocationHandler h)</span> &#123;<span class="built_in">this</span>.h = h;&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>接着，可以快速浏览一下 newProxyInstance 方法，因为其相当简单：</p><h3 id="清单-7-Proxy-静态方法-newProxyInstance"><a href="#清单-7-Proxy-静态方法-newProxyInstance" class="headerlink" title="清单 7. Proxy 静态方法 newProxyInstance"></a>清单 7. Proxy 静态方法 newProxyInstance</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 将方法调用分派到指定调用处理器</span></span><br><span class="line"><span class="comment"> * 并返回指定接口的代理类实例</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Object <span class="title function_">newProxyInstance</span><span class="params">(</span></span><br><span class="line"><span class="params">            ClassLoader loader,</span></span><br><span class="line"><span class="params">            Class&lt;?&gt;[] interfaces,</span></span><br><span class="line"><span class="params">            InvocationHandler h)</span></span><br><span class="line">            <span class="keyword">throws</span> IllegalArgumentException &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 检查 h 不为空，否则抛异常</span></span><br><span class="line">    <span class="keyword">if</span> (h == <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">NullPointerException</span>();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 获得与制定类装载器和一组接口相关的代理类类型对象</span></span><br><span class="line">    <span class="type">Class</span> <span class="variable">cl</span> <span class="operator">=</span> getProxyClass(loader, interfaces);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 通过反射获取构造函数对象并生成代理类实例</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="type">Constructor</span> <span class="variable">cons</span> <span class="operator">=</span> </span><br><span class="line">            cl.getConstructor(constructorParams);</span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> (Object) cons.newInstance(<span class="keyword">new</span> <span class="title class_">Object</span>[]&#123;h&#125;);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (NoSuchMethodException e) &#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(e.toString());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (IllegalAccessException e) &#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(e.toString());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InstantiationException e) &#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(e.toString());</span><br><span class="line">    &#125; <span class="keyword">catch</span> (InvocationTargetException e) &#123; </span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">InternalError</span>(e.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>由此可见，动态代理真正的关键是在 getProxyClass 方法，该方法负责为一组接口动态地生成代理类类型对象。在该方法内部，您将能看到 Proxy 内的各路英雄（静态变量）悉数登场。有点迫不及待了么？那就让我们一起走进 Proxy 最最神秘的殿堂去欣赏一番吧。该方法总共可以分为四个步骤：</p><p>第 1 步，对这组接口进行一定程度的安全检查，包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的，还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成，检查通过后将会得到一个包含所有接口名称的字符串数组，记为 <code>String[] interfaceNames</code> 。总体上这部分实现比较直观，所以略去大部分代码，仅保留如何判断某类或接口是否对特定类装载器可见的相关代码。</p><h3 id="清单-8-通过-Class-forName-方法判接口的可见性"><a href="#清单-8-通过-Class-forName-方法判接口的可见性" class="headerlink" title="清单 8. 通过 Class.forName 方法判接口的可见性"></a>清单 8. 通过 Class.forName 方法判接口的可见性</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// 指定接口名字、类装载器对象，</span></span><br><span class="line">    <span class="comment">// 同时制定 initializeBoolean </span></span><br><span class="line">    <span class="comment">// 为 false 表示无须初始化类</span></span><br><span class="line">    <span class="comment">// </span></span><br><span class="line">    <span class="comment">// 如果方法返回正常这表示可见，</span></span><br><span class="line">    <span class="comment">// 否则会抛出 ClassNotFoundException </span></span><br><span class="line">    <span class="comment">// 异常表示不可见</span></span><br><span class="line">    interfaceClass = </span><br><span class="line">        Class.forName(interfaceName, <span class="literal">false</span>, loader);</span><br><span class="line">&#125; <span class="keyword">catch</span> (ClassNotFoundException e) &#123;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>第 2 步，从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表，如果不存在就创建一个新的缓存表并更新到 loaderToCache。缓存表是一个 HashMap 实例，正常情况下它将存放键值对（接口名字列表，动态生成的代理类的类对象引用）。当代理类正在被创建时它会临时保存（接口名字列表，pendingGenerationMarker）。标记 pendingGenerationMarke 的作用是通知后续的同类请求（接口数组相同且组内接口排列顺序也相同）代理类正在被创建，请保持等待直至创建完成。</p><h3 id="清单-9-缓存表的使用"><a href="#清单-9-缓存表的使用" class="headerlink" title="清单 9. 缓存表的使用"></a>清单 9. 缓存表的使用</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="comment">// 以接口名字列表作为关键字获得对应 cache 值</span></span><br><span class="line">    <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> cache.get(key);</span><br><span class="line">    <span class="keyword">if</span> (value <span class="keyword">instanceof</span> Reference) &#123;</span><br><span class="line">        proxyClass = </span><br><span class="line">            (Class) ((Reference) value).get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (proxyClass != <span class="literal">null</span>) &#123;</span><br><span class="line">        <span class="comment">// 如果已经创建，直接返回</span></span><br><span class="line">        <span class="keyword">return</span> proxyClass;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (value == pendingGenerationMarker) &#123;</span><br><span class="line">        <span class="comment">// 代理类正在被创建，保持等待</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            cache.wait();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 等待被唤醒后，</span></span><br><span class="line">        <span class="comment">// 再次循环检查，</span></span><br><span class="line">        <span class="comment">// 以确保创建完成，</span></span><br><span class="line">        <span class="comment">// 否则重新等待</span></span><br><span class="line">        <span class="keyword">continue</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="comment">// 标记代理类正在被创建</span></span><br><span class="line">        cache.put(key, pendingGenerationMarker);</span><br><span class="line">        <span class="comment">// break 跳出循环已进入创建过程</span></span><br><span class="line">        <span class="keyword">break</span>;</span><br><span class="line">&#125; <span class="keyword">while</span> (<span class="literal">true</span>);</span><br><span class="line">    </span><br><span class="line"></span><br></pre></td></tr></table></figure><p>第 3 步，动态创建代理类的类对象。首先是确定代理类所在的包，其原则如前所述，如果都为 public 接口，则包名为空字符串表示顶层包；如果所有非 public 接口都在同一个包，则包名与这些接口的包名相同；如果有多个非 public 接口且不同包，则抛异常终止代理类的生成。确定了包后，就开始生成代理类的类名，同样如前所述按格式” <strong>$ProxyN</strong> ”生成。类名也确定了，接下来就是见证奇迹的发生 —— 动态生成代理类：</p><h3 id="清单-10-动态生成代理类"><a href="#清单-10-动态生成代理类" class="headerlink" title="清单 10. 动态生成代理类"></a>清单 10. 动态生成代理类</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 动态地生成代理类的字节码数组</span></span><br><span class="line"><span class="type">byte</span>[] proxyClassFile = </span><br><span class="line">    ProxyGenerator.generateProxyClass( </span><br><span class="line">    proxyName, </span><br><span class="line">    interfaces);</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 动态地定义新生成的代理类</span></span><br><span class="line">    proxyClass = </span><br><span class="line">        defineClass0(</span><br><span class="line">        loader, </span><br><span class="line">        proxyName, </span><br><span class="line">        proxyClassFile, <span class="number">0</span>,</span><br><span class="line">        proxyClassFile.length);</span><br><span class="line">    </span><br><span class="line">&#125; <span class="keyword">catch</span> (ClassFormatError e) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">IllegalArgumentException</span>(e.toString());</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 把生成的代理类的类对象  </span></span><br><span class="line"><span class="comment">// 记录进 proxyClasses 表</span></span><br><span class="line">proxyClasses.put(proxyClass, <span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>由此可见，所有的代码生成的工作都由神秘的 <strong>ProxyGenerator</strong> 所完成了，当你尝试去探索这个类时，你所能获得的信息仅仅是它位于并未公开的 sun.misc 包，有若干常量、变量和方法以完成这个神奇的代码生成的过程，但是 sun 并没有提供源代码以供研读。至于动态类的定义，则由 Proxy 的 native 静态方法 defineClass0 执行。</p><p>第 4 步，代码生成过程进入结尾部分，根据结果更新缓存表，如果成功则将代理类的类对象引用更新进缓存表，否则清除缓存表中对应关键值，最后唤醒所有可能的正在等待的线程。</p><blockquote><p>走完了以上四个步骤后，至此，所有的代理类生成细节都已介绍完毕，剩下的静态方法如 getInvocationHandler 和 isProxyClass 就显得如此的直观，只需通过查询相关变量就可以完成，所以对其的代码分析就省略了。</p></blockquote><h2 id="代理类实现推演"><a href="#代理类实现推演" class="headerlink" title="代理类实现推演"></a>代理类实现推演</h2><p>分析了 Proxy 类的源代码，相信在读者的脑海中会对 Java 动态代理机制形成一个更加清晰的理解，但是，当探索之旅在 sun.misc.ProxyGenerator 类处嘎然而止，所有的神秘都汇聚于此时，相信不少读者也会对这个 ProxyGenerator 类产生有类似的疑惑：它到底做了什么呢？它是如何生成动态代理类的代码的呢？诚然，这里也无法给出确切的答案。还是让我们带着这些疑惑，一起开始探索之旅吧。</p><p><img src="/images/post/java/proxy/02-java-dynamic-proxy-007.png" alt="02-java-dynamic-proxy-007.png"></p><p>事物往往不像其看起来的复杂，需要的是我们能够化繁为简，这样也许就能有更多拨云见日的机会。抛开所有想象中的未知而复杂的神秘因素，如果让我们用最简单的方法去实现一个代理类，唯一的要求是同样结合调用处理器实施方法的分派转发，您的第一反应将是什么呢？”听起来似乎并不是很复杂”。的确，掐指算算所涉及的工作无非包括几个反射调用，以及对原始类型数据的装箱或拆箱过程，其他的似乎都已经水到渠成。非常地好，让我们整理一下思绪，一起来完成一次完整的推演过程吧。</p><h3 id="清单-11-代理类中方法调用的分派转发推演实现"><a href="#清单-11-代理类中方法调用的分派转发推演实现" class="headerlink" title="清单 11. 代理类中方法调用的分派转发推演实现"></a>清单 11. 代理类中方法调用的分派转发推演实现</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">// 假设需代理接口 Simulator</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">Simulator</span> &#123;</span><br><span class="line">    <span class="type">short</span> <span class="title function_">simulate</span><span class="params">(<span class="type">int</span> arg1, <span class="type">long</span> arg2, String arg3)</span> </span><br><span class="line">        <span class="keyword">throws</span> ExceptionA, ExceptionB;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 假设代理类为 SimulatorProxy, 其类声明将如下</span></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimulatorProxy</span> <span class="keyword">implements</span> <span class="title class_">Simulator</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 调用处理器对象的引用</span></span><br><span class="line">    <span class="keyword">protected</span> InvocationHandler handler;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 以调用处理器为参数的构造函数</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">SimulatorProxy</span><span class="params">(InvocationHandler handler)</span>&#123;</span><br><span class="line">        <span class="built_in">this</span>.handler = handler;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 实现接口方法 simulate</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">short</span> <span class="title function_">simulate</span><span class="params">(<span class="type">int</span> arg1, <span class="type">long</span> arg2, String arg3)</span></span><br><span class="line">        <span class="keyword">throws</span> ExceptionA, ExceptionB &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 第一步是获取 simulate 方法的 Method 对象</span></span><br><span class="line">        java.lang.reflect.<span class="type">Method</span> <span class="variable">method</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span>&#123;</span><br><span class="line">            method = Simulator.class.getMethod(</span><br><span class="line">                <span class="string">&quot;simulate&quot;</span>,</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Class</span>[] &#123;<span class="type">int</span>.class, <span class="type">long</span>.class, String.class&#125; );</span><br><span class="line">        &#125; <span class="keyword">catch</span>(Exception e) &#123;</span><br><span class="line">            <span class="comment">// 异常处理 1（略）</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 第二步是调用 handler 的 invoke 方法分派转发方法调用</span></span><br><span class="line">        <span class="type">Object</span> <span class="variable">r</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            r = handler.invoke(<span class="built_in">this</span>,</span><br><span class="line">                method,</span><br><span class="line">                <span class="comment">// 对于原始类型参数需要进行装箱操作</span></span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Object</span>[] &#123;<span class="keyword">new</span> <span class="title class_">Integer</span>(arg1), <span class="keyword">new</span> <span class="title class_">Long</span>(arg2), arg3&#125;);</span><br><span class="line">        &#125;<span class="keyword">catch</span>(Throwable e) &#123;</span><br><span class="line">            <span class="comment">// 异常处理 2（略）</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">// 第三步是返回结果（返回类型是原始类型则需要进行拆箱操作）</span></span><br><span class="line">        <span class="keyword">return</span> ((Short)r).shortValue();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>模拟推演为了突出通用逻辑所以更多地关注正常流程，而淡化了错误处理，但在实际中错误处理同样非常重要。从以上的推演中我们可以得出一个非常通用的结构化流程：</p><ul><li>第一步从代理接口获取被调用的方法对象；</li><li>第二步分派方法到调用处理器执行；</li><li>第三步返回结果。</li></ul><blockquote><p>在这之中，所有的信息都是可以已知的，比如接口名、方法名、参数类型、返回类型以及所需的装箱和拆箱操作，那么既然我们手工编写是如此，那又有什么理由不相信 ProxyGenerator 不会做类似的实现呢？至少这是一种比较可能的实现。</p></blockquote><p>接下来让我们把注意力重新回到先前被淡化的错误处理上来。在异常处理 1 处，由于我们有理由确保所有的信息如接口名、方法名和参数类型都准确无误，所以这部分异常发生的概率基本为零，所以基本可以忽略。而异常处理 2 处，我们需要思考得更多一些。回想一下，接口方法可能声明支持一个异常列表，而调用处理器 invoke 方法又可能抛出与接口方法不支持的异常，再回想一下先前提及的 Java 动态代理的关于异常处理的特点，对于不支持的异常，必须抛 UndeclaredThrowableException 运行时异常。所以通过再次推演，我们可以得出一个更加清晰的异常处理 2 的情况：</p><h3 id="清单-12-细化的异常处理-2"><a href="#清单-12-细化的异常处理-2" class="headerlink" title="清单 12. 细化的异常处理 2"></a>清单 12. 细化的异常处理 2</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="type">Object</span> <span class="variable">r</span> <span class="operator">=</span> <span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    r = handler.invoke(</span><br><span class="line">            <span class="built_in">this</span>,</span><br><span class="line">            method,</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Object</span>[] &#123;</span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Integer</span>(arg1), </span><br><span class="line">                <span class="keyword">new</span> <span class="title class_">Long</span>(arg2), </span><br><span class="line">                arg3</span><br><span class="line">            &#125;</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span>( ExceptionA e) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 接口方法支持 ExceptionA，可以抛出</span></span><br><span class="line">    <span class="keyword">throw</span> e;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span>( ExceptionB e ) &#123;</span><br><span class="line">    <span class="comment">// 接口方法支持 ExceptionB，可以抛出</span></span><br><span class="line">    <span class="keyword">throw</span> e;</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span>(Throwable e) &#123;</span><br><span class="line">    <span class="comment">// 其他不支持的异常，</span></span><br><span class="line">    <span class="comment">// 一律抛 UndeclaredThrowableException</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">UndeclaredThrowableException</span>(e);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>这样我们就完成了对动态代理类的推演实现。推演实现遵循了一个相对固定的模式，可以适用于任意定义的任何接口，而且代码生成所需的信息都是可知的，那么有理由相信即使是机器自动编写的代码也有可能延续这样的风格，至少可以保证这是可行的。</p><h2 id="美中不足"><a href="#美中不足" class="headerlink" title="美中不足"></a>美中不足</h2><blockquote><p>诚然，Proxy 已经设计得非常优美，但是还是有一点点小小的遗憾之处，那就是它始终无法摆脱仅支持 interface 代理的桎梏，因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图，它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理，原因是多继承在 Java 中本质上就行不通。</p><p>有很多条理由，人们可以否定对 class 代理的必要性，但是同样有一些理由，相信支持 class 动态代理会更美好。接口和类的划分，本就不是很明显，只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量，有一种两者的混合体，它的名字叫抽象类。实现对抽象类的动态代理，相信也有其内在的价值。此外，还有一些历史遗留的类，它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种，不得不说是一个小小的遗憾。</p><p>但是，不完美并不等于不伟大，伟大是一种本质，Java 动态代理就是佐例。</p><p><strong>– 来自IBM Developer 王忠平, 何平</strong></p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>代理模式是前辈们针对一类特定问题总结出的经验结晶，并在各个领域中得以灵活应用。特别是在编程领域，不同语言根据自身的设计规范和特点融会贯通，最终都能够落实到具体的解决方案中，譬如Spring AOP在性能事务上的增强提升，或者是拦截器实现在日志和权限层面的控制过滤等等，都能做到对原接口事务的无侵入，同时还能灵活管控，大范围实施，达到我们的预期，这就是代理模式，巧妙之处。</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><ul><li><a href="https://developer.ibm.com/zh/articles/j-lo-proxy1/">Java 动态代理机制分析及扩展</a></li><li><a href="https://developer.ibm.com/zh/articles/j-lo-proxy-pattern/">代理模式原理及实例讲解</a></li><li><a href="https://java.sun.com/j2se/1.4.2/docs/guide/reflection/proxy.html">Dynamic Proxy Classes</a></li><li><a href="https://www.ibm.com/developerworks/cn/java/j-jtp08305.html">动态代理机制</a></li><li><a href="https://www.pexels.com/">图片素材</a></li><li><a href="https://www.processon.com/">流程图设计</a></li></ul><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;事实上，对于很多Java编程人员来说，可能只需要达到从入门到上手的编程水准，就能很好的完成大部分研发工作。除非自己强主动获取，或者工作倒逼你学习，否则我们好像没必要去真正了解Java编程，或者深入研究JDK运行原理、或者在实际工作中某个模块写一套设计模式、或者纠结一个线程安</description></item><item><title>译文《Java并发编程之CAS》</title><category>Java</category><category>译文集</category><category>并发编程</category><category>CAS</category><pubDate>Sat, 12 Mar 2022 05:38:04 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/03/12/translation-002-compare-and-swap/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="/images/post/java/concurrency/cas/title.jpg" alt="04-Compare-and-Swap.md#title.jpg"></p><blockquote><p>作者: 雅各布·詹科夫<br>原文: <a href="http://tutorials.jenkov.com/java-concurrency/compare-and-swap.html">http://tutorials.jenkov.com/java-concurrency/compare-and-swap.html</a><br>翻译: <a href="https://www.panshenlian.com/">潘深练</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~<br>更新: 2022-02-24</p></blockquote><p><code>CAS</code> (compare and swap) 是并发算法设计时使用的一种技术。基本上，<code>CAS</code>是将变量的值与期望值进行比较，如果值相等，则将变量的值交换设置为新值。<code>CAS</code>可能听起来有点复杂，但一旦你理解它实际上相当简单，所以让我进一步详细说明这个主题。</p><p>顺便说一句，compare and swap 有时是 <code>CAS</code> 的缩写，所以如果你看到一些关于并发的文章或视频提到 <code>CAS</code>，它很有可能是指 compare and swap（比较并交换）操作。</p><h2 id="CAS教程视频"><a href="#CAS教程视频" class="headerlink" title="CAS教程视频"></a>CAS教程视频</h2><p>如果您喜欢视频，我在这里有这个<code>CAS</code>的视频教程版本：(科学上网)<br><a href="https://www.youtube.com/watch?v=ufWVK7CHOAk&list=PLL8woMHwr36EDxjUoCzboZjedsnhLP1j4&index=18">CAS视频教程</a></p><p><img src="/images/post/java/concurrency/cas/compare-and-swap-video-screenshot.png" alt="04-Compare-and-Swap.md#compare-and-swap-video-screenshot.png"></p><h2 id="CAS的使用场景（Check-Then-Act）"><a href="#CAS的使用场景（Check-Then-Act）" class="headerlink" title="CAS的使用场景（Check Then Act）"></a>CAS的使用场景（Check Then Act）</h2><p>并发算法中常见的模式是先检查后执行（<code>check then act</code>）模式。当代码首先检查变量的值然后根据该值进行操作时，就会出现先检查后执行（<code>check then act</code>）模式。这是一个简单的例子：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">ProblematicLock</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(<span class="built_in">this</span>.locked) &#123;</span><br><span class="line">            <span class="comment">// 忙等待 - 直到 this.locked == false</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>.locked = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.locked = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此代码不是多线程锁的 <code>100%</code> 正确实现。这就是我给它命名的原因 <code>ProblematicLock</code> (问题锁) 。然而，我创建了这个错误的实现来说明如何通过<code>CAS</code>功能来解决它​​的问题。</p><p>该<code>lock()</code>方法首先检查成员变量是否<code>locked</code>等于<code>false</code>。这是在<code>while-loop</code>内部完成的。如果<code>locked</code>变量是<code>false</code>，则该<code>lock()</code>方法离开<code>while</code>循环并设置<code>locked</code>为<code>true</code>。换句话说，该 <code>lock()</code>方法首先检查变量的值<code>locked</code>，然后根据该检查进行操作。先检查，再执行。</p><p>如果多个线程几乎同时刻访问同一个 <code>ProblematicLock</code> 实例，那以上的 <code>lock()</code> 方法将会有一些问题，例如：</p><p>如果线程 A 检查<code>locked</code>的值为 <code>false</code>（预期值），它将退出 <code>while-loop</code> 循环执行后续的逻辑。如果此时有个线程B在线程A将<code>locked</code>值设置为 <code>true</code> 之前也检查了 <code>locked</code> 的值，那么线程B也将退出 <code>while-loop</code> 循环执行后续的逻辑。这是一个典型的资源竞争问题。</p><h2 id="先检查后执行（Check-Then-Act）必须是原子性的"><a href="#先检查后执行（Check-Then-Act）必须是原子性的" class="headerlink" title="先检查后执行（Check Then Act）必须是原子性的"></a>先检查后执行（Check Then Act）必须是原子性的</h2><p>为了在多线程应用程序中正常工作（以避免资源竞争），先检查后执行（<code>Check Then Act</code>）必须是原子性的。原子性的意思是检查和执行动作都作为原子（不可分割的）代码块执行。任何开始执行该块的线程都将完成该块的执行，而不受其他线程的干扰。不允许其他线程在同一时刻执行相同原子块。</p><p>使<code>Java</code>代码块具有原子性的一种简单方法是使用<code>Java</code>的<code>synchronized</code>关键字对其进行标记。可以参阅<a href="http://concurrent-programming.panshenlian.com/#/zh-cn/02-Java-Synchronized-Blocks"> 关于synchronized</a> 的内容。这是<code>ProblematicLock</code>之前使用<code>synchronized</code>关键字将<code>lock()</code>方法转换为原子代码块的方法：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyLock</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="type">boolean</span> <span class="variable">locked</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">while</span>(<span class="built_in">this</span>.locked) &#123;</span><br><span class="line">            <span class="comment">// 忙等待 - 直到 this.locked == false</span></span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="built_in">this</span>.locked = <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.locked = <span class="literal">false</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在方法<code>lock()</code>已申明同步，因此同一实例的<code>lock()</code>方法在同一时刻只允许被一个线程访问执行。相当于 <code>lock()</code> 方法是原子性的。</p><h2 id="阻塞线程的代价很大"><a href="#阻塞线程的代价很大" class="headerlink" title="阻塞线程的代价很大"></a>阻塞线程的代价很大</h2><p>当两个线程试图同时进入<code>Java</code>中的一个同步块时，其中一个线程将被阻塞，而另一个线程将被允许进入同步块。当进入同步块的线程再次退出该块时，等待中的线程才会被允许进入该块。</p><p>如果线程被允许访问执行，那么进入一段同步代码块的代价并不大。但是如果因为已有一个线程在同步块中执行导致另一个线程被迫等阻塞，那么这个阻塞线程的代价就很大。</p><p>此外，当同步块再次空闲时，<strong>您无法准确地确定何时能解除阻塞的线程</strong>。这通常取决于<code>操作系统</code>或<code>执行平台</code>来 <strong>协调</strong> 阻塞线程的 <strong>阻塞解除</strong>。当然，在阻塞线程被解除阻塞并允许进入之前不会花费几秒钟或几分钟，但是可能会浪费一些时间用于阻塞线程，因为它本来可以访问共享数据结构的。这在此处进行了说明：</p><p><img src="/images/post/java/concurrency/cas/compare-and-swap-1.png" alt="04-Compare-and-Swap.md#compare-and-swap-1.png"></p><h2 id="硬件提供的原子性CAS操作"><a href="#硬件提供的原子性CAS操作" class="headerlink" title="硬件提供的原子性CAS操作"></a>硬件提供的原子性CAS操作</h2><p>现代 <code>CPU</code> 内置了对<code>CAS</code>的原子性操作的支持。在某些情况下，可以使用<code>CAS</code>操作来替代同步块或其他阻塞数据结构。<code>CPU</code> 保证一次只有一个线程可以执行<code>CAS</code>操作，即使跨 <code>CPU</code> 内核也是如此。稍后在代码中有示例。</p><p>当使用硬件或 <code>CPU</code> 提供的<code>CAS</code>功能而不是操作系统或执行平台提供的 <code>synchronized</code>、<code>lock</code>、<code>mutex</code>（互斥锁） 等时，操作系统或执行平台不需要处理线程的阻塞和解除阻塞。这使得使用<code>CAS</code>的线程等待执行操作的时间更短，并且拥有更少的拥塞和更高的吞吐量。如下图所示：</p><p><img src="/images/post/java/concurrency/cas/compare-and-swap-2.png" alt="04-Compare-and-Swap.md#compare-and-swap-2.png"></p><p>如您所见，试图进入共享数据结构的线程永远不会被完全阻塞。它不断尝试执行<code>CAS</code>操作，直到成功，并被允许访问共享数据结构。这样线程可以进入共享数据结构之前的延迟被最小化。</p><p>当然，如果线程在重复执行<code>CAS</code>的过程中等待很长时间，可能会浪费大量的<code>CPU</code>周期，而这些<code>CPU</code>周期本来可以用在其他任务（其他线程）上。但在许多情况下，情况并非如此。这取决于共享数据结构被另一个线程使用多长时间。实际上，共享数据结构的使用时间不长，因此上述情况不应该经常发生。但同样这取决于具体情况、代码、数据结构、尝试访问数据结构的线程数、系统负载等。相反，阻塞的线程根本不使用<code>CPU</code>。</p><h2 id="Java中的CAS"><a href="#Java中的CAS" class="headerlink" title="Java中的CAS"></a>Java中的CAS</h2><p>从 <code>Java 5</code> 开始，您可以通过<code>java.util.concurrent.atomic</code>包中的一些新的原子类访问 <code>CPU</code> 级别的<code>CAS</code>方法。这些类有：</p><ul><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomicboolean.html">AtomicBoolean</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomicinteger.html">AtomicInteger</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomiclong.html">AtomicLong</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomicreference.html">AtomicReference</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomicstampedreference.html">AtomicStampedReference</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomicintegerarray.html">AtomicIntegerArray</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomiclongarray.html">AtomicLongArray</a></li><li><a href="http://tutorials.jenkov.com/java-util-concurrent/atomicreferencearray.html">AtomicReferenceArray</a></li></ul><p>使用 <code>Java 5+</code> 附带的 <code>CAS</code> 功能而不是自己实现的优势在于，<code>Java 5+</code> 中内置的 <code>CAS</code> 功能允许您的应用程序利用 <code>CPU</code> 的底层能力执行<code>CAS</code>操作。这使您的<code>CAS</code>实现代码更快。</p><h2 id="CAS的保障性"><a href="#CAS的保障性" class="headerlink" title="CAS的保障性"></a>CAS的保障性</h2><p><code>CAS</code>功能可用于保护临界区（<code>Critical Section</code>），从而防止多个线程同时执行临界区。</p><p>?&gt; <strong>critical section</strong> 是每个线程中访问临界资源的那段代码，不论是硬件临界资源，还是软件临界资源，多个线程必须互斥地对它进行访问。每个线程中访问临界资源的那段代码称为临界区（<code>Critical Section</code>）。每个线程中访问临界资源的那段程序称为临界区（<code>Critical Section</code>）（临界资源是一次仅允许一个线程使用的<strong>共享资源</strong>）。每次只准许一个线程进入临界区，进入后不允许其他线程进入。</p><p>下面的一个示例，展示了如何使用<code>AtomicBoolean</code>类的<code>CAS</code>功能来实现前面显示的<code>lock()</code>方法并因此起到保障作用（一次只有一个线程可以退出该<code>lock()</code>方法）。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CompareAndSwapLock</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">AtomicBoolean</span> <span class="variable">locked</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicBoolean</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">unlock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.locked.set(<span class="literal">false</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">lock</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">while</span>(!<span class="built_in">this</span>.locked.compareAndSet(<span class="literal">false</span>, <span class="literal">true</span>)) &#123;</span><br><span class="line">            <span class="comment">// busy wait - until compareAndSet() succeeds</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意这个<code>locked</code>变量不再是一个布尔类型而是<code>AtomicBoolean</code>类型，此类有一个<code>compareAndSet()</code>方法，会把实例的值（变量locked）与第一个参数（<code>false</code>）进行比较，如果比较结果相同（即locked的值等于第一个参数false），那么会将实例的值 <code>locked</code> 与期望值<code>true</code>交换（即把locked变量设置为true，表示锁住了）。如果交换成功则<code>compareAndSet()</code>方法会返回 <code>true</code>，如果没有交换成功则返回 <code>false</code>。</p><p>在上面的例子中，<code>compareAndSet()</code>方法调用比较了<code>locked</code>变量值与<code>false</code>值，如果<code>locked</code>变量值的结果值就是<code>false</code>，那么就是设置<code>locked</code>值为<code>true</code>。</p><p>由于一次只能允许一个线程执行该<code>compareAndSet()</code>方法，因此只有一个线程能够看到AtomicBoolean实例值为 <code>false</code>，从而将其交换为<code>true</code>。因此，每次只有一个线程能够退出<code>while-loop</code>（while循环），通过调用 <code>unlock()</code> 方法设置 <code>locked</code> 为 <code>false</code> 使得每次只有一个线程的 <code>CompareAndSwapLock</code> 是解锁状态的。 </p><h2 id="CAS实现乐观锁"><a href="#CAS实现乐观锁" class="headerlink" title="CAS实现乐观锁"></a>CAS实现乐观锁</h2><p>也可以使用<code>CAS</code>功能作为乐观锁机制。乐观锁机制允许多个线程同时进入临界区，但只允许其中一个线程在临界区结束时提交其工作。</p><p>下面是一个使用乐观锁策略的并发计数器类示例：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">OptimisticLockCounter</span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="type">AtomicLong</span> <span class="variable">count</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">AtomicLong</span>();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">inc</span><span class="params">()</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="type">boolean</span> <span class="variable">incSuccessful</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">while</span>(!incSuccessful) &#123;</span><br><span class="line">            <span class="type">long</span> <span class="variable">value</span> <span class="operator">=</span> <span class="built_in">this</span>.count.get();</span><br><span class="line">            <span class="type">long</span> <span class="variable">newValue</span> <span class="operator">=</span> value + <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line">            incSuccessful = <span class="built_in">this</span>.count.compareAndSet(value, newValue);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="type">long</span> <span class="title function_">getCount</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">this</span>.count.get();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>请注意 <code>inc()</code> 方法是如何从 <code>AtomicLong</code>实例变量<code>count</code>中获取现有计数值的。然后根据旧值计算出新值。最后，<code>inc()</code> 方法尝试通过调用<code>AtomicLong</code>实例的<code>compareAndSet()</code>方法来设置新值。</p><p>如果<code>AtomicLong</code>实例值<code>count</code>在比较时仍然拥有与上次获取时（<code>long value = this.count.get()</code>）的值相同，那么<code>compareAndSet()</code>会执行成功。但是假如有另一个线程在同一时刻已经调用增加了<code>AtomicLong</code>实例值（指有一个线程在之前已经调用成功<code>compareAndSet()</code>方法了，一般认为是<strong>资源竞争</strong>），则<code>compareAndSet()</code>调用将失败，因为预期值<code>value</code>不再等于存储在中的值<code>AtomicLong</code>（原值已经被前一个线程更改过）。在这种情况下，<code>inc()</code>方法将在 <code>while-loop</code>（while循环）中进行另外一次迭代并尝试再次增加<code>AtomicLong</code>值。</p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="/images/post/java/concurrency/cas/title.jpg" alt="04-Compare-and-Swap.md#title.jpg"&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;作者: 雅各布·詹科夫&lt;br&gt;原文: &lt;a </description></item><item><title>37岁前的37件事</title><category>人生目标</category><pubDate>Wed, 2 Mar 2022 00:49:17 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/03/02/live-002-my-37-before-37/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="/images/post/live/37/title.jpg" alt="37岁前的37件事"></p><p>以下是我希望在年满37岁之前（2026.1）做到的37件事。有些是目标，有些是体验类的，受陈成老师、<a href="https://critter.blog/2022/02/28/40-before-40/">Mike Crittenden</a> 启发，遂而计划，暂时只梳理出一部分，后续会慢慢补充到37个。</p><p>以下就是我37岁前的37件事：</p><ol><li>结交非工作上的朋友</li><li>写一本小册</li><li>开一个视频频道</li><li>开一个播客频道</li><li>做一次视频直播</li><li>阅读100本书</li><li>写400篇文章</li><li>超级大脑项目开源</li><li>精通一门新的编程语言</li><li>置换一套大房</li><li>给老婆买辆专属车</li><li>米其林餐厅吃饭</li><li>做大会发讲人</li><li>坐头等舱</li><li>设立遗嘱</li><li>和孩子看日出</li><li>和孩子野营</li><li>和孩子一起学会游泳</li><li>带父母进行一次长途旅行</li><li>体重达到73kg</li><li>坐摩天轮</li><li>攀岩</li><li>看喜欢乐队的现场表演</li><li>看喜欢球队的现场比赛</li><li>养一只宠物</li><li>英语阅读无障碍</li><li>债务还清（房贷除外）</li><li>参加一次亲子马拉松</li><li>加入摄影爱好者组织</li><li>加入美食爱好者组织</li><li>加入虎爸鸡娃队伍</li><li>加入读书会</li><li>带小孩去10座城市以上</li><li>二胎（目标是三胎！）</li><li></li><li></li><li></li></ol><p>（待完善）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="/images/post/live/37/title.jpg" alt="37岁前的37件事"&gt;&lt;/p&gt;
&lt;p&gt;以下是我希望在年满37岁之前（2026.1）做到的37件事。有些是目标，有些是体验类的，受陈成老师、&lt;a href="https://crit</description></item><item><title>2022年的33个目标</title><category>人生目标</category><pubDate>Wed, 2 Mar 2022 00:33:08 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/03/02/live-001-my-33-before-33/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="/images/post/live/33/title.jpg" alt="2022年的33个目标"></p><h2 id="目标类别"><a href="#目标类别" class="headerlink" title="目标类别"></a>目标类别</h2><p>记得从2020年开始，每年年初都会给自己设定一年的目标，类别基本会涵盖：</p><ul><li>职业财富</li><li>亲情陪伴</li><li>家庭健康</li><li>社会公益</li><li>人际交往</li><li>个人成长</li></ul><p><img src="/images/post/live/goal/my-33-before-33-001.png" alt="2022年大作战【自律】"></p><h2 id="目标标签"><a href="#目标标签" class="headerlink" title="目标标签"></a>目标标签</h2><p>而且每年的目标都会贴一个不一样的标签，例如：</p><ul><li>2020年大作战【思想要爆炸】</li><li>2021年大作战【要坚持要上进】</li><li>2022年大作战【自律】</li></ul><p><img src="/images/post/live/goal/my-33-before-33-002.png" alt="目标标签"></p><h2 id="目标-Flag"><a href="#目标-Flag" class="headerlink" title="目标 Flag"></a>目标 Flag</h2><p>每个类别会具体细拆出每一个具体的项，目前我基本都是记录成一份 Excel 文档，存放在云盘上，无论出差或者出游，都方便我每天做一次更新，当然偶尔会有一些天会遗忘掉，但也都在后续补上，我的2022年Flag基本有这些：</p><h3 id="职业财富"><a href="#职业财富" class="headerlink" title="职业财富"></a>职业财富</h3><ol><li>年薪超过**W</li><li>还清债务**W+</li></ol><h3 id="家庭生活"><a href="#家庭生活" class="headerlink" title="家庭生活"></a>家庭生活</h3><ol start="3"><li>广东探亲3次</li><li>河南探亲3次</li><li>学习育儿成长知识150回</li><li>亲手下厨精致大餐2次</li><li>陪伴孩子运动/活动30次</li><li>陪伴孩子阅读50次</li><li>送老婆礼物12次</li><li>送孩子礼物12次</li><li>送父母礼物4次</li><li>父母电话聊天100次</li><li>家庭大扫除4次</li><li>家庭跨城市旅行2次</li></ol><h3 id="家庭健康"><a href="#家庭健康" class="headerlink" title="家庭健康"></a>家庭健康</h3><ol start="15"><li>200天作息早7晚11</li><li>参加1次半马/亲子马</li><li>绳跳/开合跳/深蹲200天</li><li>(61.5~70)体重增加8.5kg</li><li>小腿复检1次</li><li>家庭体检1次</li><li>购入家庭医疗保险1套</li><li>母亲腿部检查1次</li><li>购入父母医疗保险1套</li><li>家庭洗牙2次</li></ol><h3 id="社会公益"><a href="#社会公益" class="headerlink" title="社会公益"></a>社会公益</h3><ol start="25"><li>当志愿者2次</li></ol><h3 id="人际交往"><a href="#人际交往" class="headerlink" title="人际交往"></a>人际交往</h3><ol start="26"><li>同事轰趴2次</li><li>参加社群活动2次</li></ol><h3 id="个人成长"><a href="#个人成长" class="headerlink" title="个人成长"></a>个人成长</h3><ol start="28"><li>清理RSS、Github、知识星球120次</li><li>24篇文章</li><li>读书20本</li><li>翻译10篇英文文章</li><li>20个产品功能</li><li>公众号新增5000粉丝</li></ol><h2 id="想法"><a href="#想法" class="headerlink" title="想法"></a>想法</h2><p>2022年目标我是在1月初就梳理完，然后确定后一般不会大改，只会在3-4月份之前还进行一次微调，之后就基本按部就班，努力冲目标。</p><p>本来没想着发出来，不过今天早上正好看到陈成老师也发了一版，我想了想要不也发出来吧，都说 Flag 要发出来才实现得快，所以也发出来，也更能倒逼自己去完成，具体完成的情况如何，我们年终总结。</p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;&lt;img src="/images/post/live/33/title.jpg" alt="2022年的33个目标"&gt;&lt;/p&gt;
&lt;h2 id="目标类别"&gt;&lt;a href="#目标类别" class="headerlink" title="目标类别"&gt;&lt;/a&gt;目标类别&lt;/</description></item><item><title>伏羲山游记</title><category>旅游</category><pubDate>Mon, 28 Feb 2022 02:57:22 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/02/28/live-003-travel-of-fuxi-mountain/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p>有点亏欠孩子，他已经过了4岁生日，我还没带他去爬过山。平时在幼儿园里可能经常听老师同学们讨论有关爬山的趣事，我下班回到家偶尔能听他提起一些词：登山杖、望远镜、探险、寻宝藏…等等，我知道他很向往。</p><p>刚好这一次周末双休，天气也是正好，15°C 这样，特别适合野外爬山，我赶紧提前跟老婆合计好，选了一个适合孩子游玩的景点——伏羲山。</p><p>伏羲山主要有四处景点：红石林、三泉湖、伏羲大峡谷、云上牧场。伏羲山是近几年才开发的，开发力度很大，目前还在持续开发中，但是大部分都已经对外开放了，四处景区之间山山相连，有些是开车10分钟到达，有些是通过索道缆车30分钟可以相互到达，所以很集中，基本两天可以游完。</p><p>由于平时周末经常是抱着笔记本在星巴克一坐就是一天，或者陪孩子参加各种特长补习班的，所以基本上没怎么接触大自然，特别伏羲山这种白云、蓝天、新鲜空气…另外伏羲山还给我们带了很多惊喜，例如：小鹿森林、玻璃栈桥、索道、魔毯、登山体验、以及人好得特别的民宿老板等等。</p><p>这是我们记录的部分时光片段：</p><p><img src="/images/post/live/travel-fuxi-mountain/01.jpg" alt="travel-fuxi-mountain-1"></p><p><img src="/images/post/live/travel-fuxi-mountain/02.jpg" alt="travel-fuxi-mountain-2"></p><p><img src="/images/post/live/travel-fuxi-mountain/03.jpg" alt="travel-fuxi-mountain-3"></p><p><img src="/images/post/live/travel-fuxi-mountain/04.jpg" alt="travel-fuxi-mountain-4"></p><p><img src="/images/post/live/travel-fuxi-mountain/06.jpg" alt="travel-fuxi-mountain-6"></p><p><img src="/images/post/live/travel-fuxi-mountain/08.jpg" alt="travel-fuxi-mountain-8"></p><p><img src="/images/post/live/travel-fuxi-mountain/09.jpg" alt="travel-fuxi-mountain-9"></p><p><img src="/images/post/live/travel-fuxi-mountain/10.jpg" alt="travel-fuxi-mountain-10"></p><p><img src="/images/post/live/travel-fuxi-mountain/11.jpg" alt="travel-fuxi-mountain-11"></p><p>（本篇完）</p>]]></content:encoded><description>&lt;p&gt;有点亏欠孩子，他已经过了4岁生日，我还没带他去爬过山。平时在幼儿园里可能经常听老师同学们讨论有关爬山的趣事，我下班回到家偶尔能听他提起一些词：登山杖、望远镜、探险、寻宝藏…等等，我知道他很向往。&lt;/p&gt;
&lt;p&gt;刚好这一次周末双休，天气也是正好，15°C 这样，特别适合野外爬</description></item><item><title>译文《Java并发与多线程介绍》</title><category>Java</category><category>译文集</category><category>并发编程</category><category>多线程</category><pubDate>Fri, 25 Feb 2022 09:08:11 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2022/02/25/translation-001-java-concurrency-and-multithreading/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/java/concurrency/multithreading/title.jpg" alt="translation-004-java-concurrency-and-multithreading-tutorial.md#title.jpg"></p><blockquote><p>作者: 雅各布·詹科夫<br>原文: <a href="http://tutorials.jenkov.com/java-concurrency/index.html">http://tutorials.jenkov.com/java-concurrency/index.html</a><br>翻译: <a href="https://www.panshenlian.com/">潘深练</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~<br>更新: 2022-02-23</p></blockquote><p>Java并发是一个涵盖Java平台上多线程、并发和并行性的术语。这里面就涉及到Java并发工具、并发问题和对应的解决方案。本Java并发编程文档内容基本涵盖了Java多线程知识中关于多线程、并发构造、并发问题、并发代价以及并发优点相关的核心概念。</p><h2 id="Java并发与多线程学习视频"><a href="#Java并发与多线程学习视频" class="headerlink" title="Java并发与多线程学习视频"></a>Java并发与多线程学习视频</h2><p>如果您喜欢视频，这里有一个视频播放列表，其中涵盖了本教程系列涵盖的一些相同主题。您可以在此处找到视频播放列表：</p><p><a href="https://www.youtube.com/playlist?list=PLL8woMHwr36EDxjUoCzboZjedsnhLP1j4">Java 并发与多线程 - 视频播放列表</a></p><h2 id="什么是多线程？"><a href="#什么是多线程？" class="headerlink" title="什么是多线程？"></a>什么是多线程？</h2><p>多线程指的是在同一个应用程序中存在多个执行线程。每个线程就像都拥有一个独立的CPU在执行你的应用程序。因此，多线程应用程序看起来就像具备多CPU能力一样，能在同时刻执行应用程序中的不同代码片段。</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/tutorial/introduction-1.png" alt="00-java-concurrency#introduction-1.png"></p><p>但是，线程不等同于CPU，通常单个CPU在多个线程之间执行时间是共享的，只不过CPU在每个线程获得一定执行时间片之后运行切换。当然，同一个应用程序中的不同线程也可以让不同的CPU来执行。</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/tutorial/introduction-2.png" alt="00-java-concurrency#introduction-2.png"></p><h2 id="为什么要有多线程？"><a href="#为什么要有多线程？" class="headerlink" title="为什么要有多线程？"></a>为什么要有多线程？</h2><p>关于为什么要在应用程序中使用多线程，通常有几个原因：</p><ul><li>更好地利用单个CPU。</li><li>更好地利用多个CPU或多核CPU。</li><li>在响应性方面更好的用户体验。</li><li>在公平性方面更好的用户体验。</li></ul><p>我将在以下几点中更详细地解释这些原因。</p><h3 id="更好地利用单个-CPU"><a href="#更好地利用单个-CPU" class="headerlink" title="更好地利用单个 CPU"></a>更好地利用单个 CPU</h3><p>最常见的原因之一是能够有效地利用计算机中的资源。 例如，一个线程通过网络发送请求之后处于等待响应中，此时其他线程可以利用CPU来做其他事情。另外，如果计算机是多CPU，或者多核CPU，那么多线程可以帮助您的应用程序有效利用这些额外的CPU内核资源。</p><h3 id="更好地利用多个CPU或多核CPU"><a href="#更好地利用多个CPU或多核CPU" class="headerlink" title="更好地利用多个CPU或多核CPU"></a>更好地利用多个CPU或多核CPU</h3><p>如果计算机包含多个CPU或多核CPU，那么您需要使用多个线程来让您的应用程序能够更好地利用所有CPU或多核CPU资源。单个线程最多只能使用单个 CPU，而且正如以上提到的，有时甚至不能完全使用单个CPU（在多个线程共享CPU的情况下）。</p><h3 id="在响应性方面更好的用户体验"><a href="#在响应性方面更好的用户体验" class="headerlink" title="在响应性方面更好的用户体验"></a>在响应性方面更好的用户体验</h3><p>使用多线程的另一个原因是提升用户体验。例如，你在用户图形界面（GUI）上点击一个按钮，此时按钮会发起一个网络请求，那么处理请求的线程如何执行就会显得很重要。如果你让当前处理线程也来负责用户图形界面的更新，那么由于这个线程处于等待请求响应中，用户会看到当前的用户图形界面被挂起。相反，如果这个请求让另外的后台线程去处理，那么用户图形界面线程在此期间就可以先自由地响应用户。</p><p>?&gt; 基本的设计理念：异步线程处理业务，同步线程提前响应，提供用户体验。</p><h3 id="在公平性方面更好的用户体验"><a href="#在公平性方面更好的用户体验" class="headerlink" title="在公平性方面更好的用户体验"></a>在公平性方面更好的用户体验</h3><p>第四个原因是所有用户之间更公平地共享计算机资源。例如，想象有一台服务器接收多个客户端的请求，并且只有一个线程在处理这些请求。如果其中一个客户端的请求处理了很长一段时间才完成，那么在这段时间内其他客户端请求都必须等待。因此通过多线程的设计，让每一个客户端请求都单独处理，并且不会让任何一个任务完全独占CPU。</p><h2 id="多线程与多任务"><a href="#多线程与多任务" class="headerlink" title="多线程与多任务"></a>多线程与多任务</h2><p>在过去单CPU时代，单任务在一个时间点只能执行单一程序。大多数小型计算机的能力也都不足以同时执行多个程序，所以多线程、多任务都没有被尝试过。公平地说，与个人计算机相比，许多大型机系统已经能够一次执行多个程序很久了。</p><h3 id="多任务处理"><a href="#多任务处理" class="headerlink" title="多任务处理"></a>多任务处理</h3><p>后来发展到多任务处理阶段，这意味着计算机在同一时间点并行执行多任务或多进程。不过这并不是真正意义上的“同时”。而是多个任务或进程共享一个CPU，并交由操作系统来完成多任务间对CPU的运行切换，以是的每个任务都有机会获得一定的时间片运行。</p><p>随着多任务处理的出现，软件开发人员面临新的挑战，程序不在能假设独占所有的CPU时间、所有的内存和其他计算机资源。一个好的程序榜样是在其不再使用这些资源时对其进行释放，以使得其他程序能有机会使用这些资源。</p><h3 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h3><p>再后来发展到多线程技术，使得在一个程序内部能拥有多个线程并行执行。一个线程的执行可以被认为是一个CPU在执行该程序。当一个程序运行在多线程下，就好像有多个CPU在同时执行该程序。</p><h2 id="多线程是具有挑战的"><a href="#多线程是具有挑战的" class="headerlink" title="多线程是具有挑战的"></a>多线程是具有挑战的</h2><p>在很多类型的程序中多线程被用来提升性能，但是多线程比多任务更加有挑战性。多线程是在同一个程序内部并行执行，因此会对相同的内存空间进行并发读写操作。这可能是在单线程程序中从来不会遇到的问题。其中的一些错误也未必会在单CPU机器上出现，因为两个线程从来不会得到真正的并行执行。然而，更现代的计算机伴随着多核CPU的出现，也就意味着不同的CPU内核能够真正意义并行执行不同的线程。</p><p><img src="https://www.panshenlian.com/images/post/java/concurrency/tutorial/java-concurrency-tutorial-introduction-1.png" alt="00-java-concurrency#java-concurrency-tutorial-introduction-1.png"></p><p>如果一个线程在读一个内存时，另一个线程正向该内存进行写操作，那进行读操作的那个线程将获得什么结果呢？是写操作之前旧的值？还是写操作成功之后的新值？或是一半新一半旧的值？或者，如果是两个线程同时写同一个内存，在操作完成后将会是什么结果呢？是第一个线程写入的值？还是第二个线程写入的值？还是两个线程写入的一个混合值？</p><p>如果没有适当的预防措施，任何这些结果都是可能的。这种行为甚至无法预测。结果可能会不时改变。因此，作为开发人员，了解如何采取正确的预防措施——学习控制线程如何访问共享资源，如内存、文件、数据库等等就显得非常重要，这也是本Java并发编程内容所涉及的主题之一。</p><h2 id="Java中的多线程和并发"><a href="#Java中的多线程和并发" class="headerlink" title="Java中的多线程和并发"></a>Java中的多线程和并发</h2><p>Java是最先支持多线程的开发的语言之一，Java从一开始就支持了多线程能力，因此Java开发者能常遇到上面描述的问题场景。这也是我想为Java并发技术而写这篇系列的原因。作为对自己的笔记，和对其他Java开发的追随者都可获益的。</p><p>该系列主要关注Java多线程，但有些在多线程中出现的问题会和多任务以及分布式系统中出现的存在类似，因此该系列会将多任务和分布式系统方面作为参考，所以叫法上称为“并发性”，而不是“多线程”。</p><h2 id="并发模型"><a href="#并发模型" class="headerlink" title="并发模型"></a>并发模型</h2><p>第一种Java并发模型，指的是在同一个应用程序中并行执行的线程会共享状态（共享状态通常指的是某些数据）。这种典型的并发模型称为 “共享状态并发模型” 。很多语言的并发架构和工具包，都是基于这种并发模型设计的。</p><p>然而，在第一本Java并发书籍编写以来，同时在 Java 5 并发工具包发布至今，并发架构和设计发生了很多变化。</p><p>由于共享状态并发模型会带来一些难以优雅解决的并发问题，所以一种称为 “无共享” 或 “分离状态” 的替代并发模型已经流行起来，在分离状态并发模型中，线程不共享任何对象或数据，这避免了共享状态并发模型的很多并发访问问题。</p><p>近年来，异步 “分离状态” 平台和工具包如 Netty、Vert.x 和 Play / Akka 和 Qbit 已经出现，也发布了新的非阻塞并发算法，并且新的非阻塞工具（如 LMax Disrupter）也已添加到我们的工具包中。Java 7 中的 Fork 和 Join 框架以及 Java 8 中的集合流 API 都引入了这种新的函数式并行编程。</p><p>随着所有这些新的发展，我是时候更新这个Java并发教程了。因此，本教程再次进行完善。在时间允许的情况下回继续编写新教程、陆续发布。</p><p>（本篇完）</p><blockquote><p>作者: 雅各布·詹科夫<br>原文: <a href="http://tutorials.jenkov.com/java-concurrency/index.html">http://tutorials.jenkov.com/java-concurrency/index.html</a><br>翻译: <a href="https://www.panshenlian.com/">潘深练</a> 如您有更好的翻译版本，欢迎 ❤️ 提交 <a href="https://github.com/senlypan/concurrent-programming-docs/issues">issue</a> 或投稿哦~<br>更新: 2022-02-23</p></blockquote>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/java/concurrency/multithreading/title.jpg" alt="translation-004-java-concurrency-and-mu</description></item><item><title>Mybatis系列全解（八）：Mybatis的9大动态SQL标签你知道几个？</title><category>Mybatis</category><pubDate>Thu, 4 Mar 2021 00:20:00 GMT</pubDate><guid isPermaLink="false">https://www.panshenlian.com/2021/03/04/mybatis-008-dynamic-sql/</guid><content:encoded xmlns:content="http://purl.org/rss/1.0/modules/content/"><![CDATA[<p><img src="https://www.panshenlian.com/images/post/java/mybatis/title/08-title.jpg"></p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/slogan_start.png"></p><p>2021年，仰望天空，脚踏实地。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/slogan_end.png"></p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/wechat_newyear.png"></p><blockquote><p>这算是春节后首篇 Mybatis 文了~ </p><p>跨了个年感觉写了有半个世纪 …  </p><p>借着女神节 ヾ(◍°∇°◍)ﾉﾞ</p><p>提前祝男神女神们越靓越富越嗨森！</p><p>上图保存可做朋友圈封面图 ~</p></blockquote><h3 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h3><p>本节我们介绍 Mybatis 的强大特性之一：<strong>动态 SQL</strong> ，从动态 SQL 的诞生背景与基础概念，到动态 SQL 的标签成员及基本用法，我们徐徐道来，再结合框架源码，剖析动态 SQL （标签）的底层原理，最终在文末吐槽一下：在无动态 SQL 特性（标签）之前，我们会常常掉进哪些可恶的坑吧~</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/d1.png"></p><p><strong>Mybatis 全解系列脑图全览一直在更新哦</strong></p><iframe id="embed_dom" name="embed_dom" frameborder="0" style="display:block;width:100%; height:500px;" src="https://www.processon.com/embed/5fb88348f346fb5f0e298069"></iframe><h4 id="Mybaits系列全解-传送门"><a href="#Mybaits系列全解-传送门" class="headerlink" title="Mybaits系列全解 ( 传送门 )"></a>Mybaits系列全解 ( 传送门 )</h4><ul><li><a href="/2020/11/16/mybatis-001-hand-write-frame/">Mybatis系列全解（一）：手写一套持久层框架</a></li><li><a href="/2020/11/28/mybatis-002-introduct-and-environment-construction/">Mybatis系列全解（二）：Mybatis简介与环境搭建</a></li><li><a href="/2020/12/01/mybatis-003-usage-for-crud/">Mybatis系列全解（三）：Mybatis简单CRUD使用介绍</a></li><li><a href="/2020/12/10/mybatis-004-xml-config-file/">Mybatis系列全解（四）：全网最全！Mybatis配置文件XML全貌详解</a></li><li><a href="/2020/12/18/mybatis-005-mapping-file/">Mybatis系列全解（五）：全网最全！详解Mybatis的Mapper映射文件</a></li><li><a href="/2021/01/11/mybatis-006-core-api/">Mybatis系列全解（六）：Mybatis最硬核的API你知道几个？</a></li><li><a href="/2021/01/25/mybatis-007-two-impl-of-dao-layer">Mybatis系列全解（七）：全息视角看Dao层两种实现方式之传统与代理？</a></li><li><a href="/2021/03/04/mybatis-008-dynamic-sql">Mybatis系列全解（八）：Mybatis的9大动态SQL标签你知道几个？</a></li><li>Mybatis系列全解（九）：Mybatis的复杂映射</li><li>Mybatis系列全解（十）：Mybatis注解开发</li><li>Mybatis系列全解（十一）：Mybatis缓存全解</li><li>Mybatis系列全解（十二）：Mybatis插件开发</li><li>Mybatis系列全解（十三）：Mybatis代码生成器</li><li>Mybatis系列全解（十四）：Spring集成Mybatis</li><li>Mybatis系列全解（十五）：SpringBoot集成Mybatis</li><li>Mybatis系列全解（十六）：Mybatis源码剖析</li></ul><h3 id="本文目录"><a href="#本文目录" class="headerlink" title="本文目录"></a>本文目录</h3><p><strong>1、什么是动态SQL</strong></p><p><strong>2、动态SQL的诞生记</strong></p><p><strong>3、动态SQL标签的9大标签</strong></p><p><strong>4、动态SQL的底层原理</strong></p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/01.png"></p><h4 id="1、什么是动态SQL-？"><a href="#1、什么是动态SQL-？" class="headerlink" title="1、什么是动态SQL  ？"></a>1、什么是动态SQL  ？</h4><p>关于动态 SQL ，允许我们理解为 “ <strong>动态的 SQL</strong> ”，其中 “ 动态的 ” 是形容词，“ SQL ” 是名词，那显然我们需要先理解名词，毕竟形容词仅仅代表它的某种形态或者某种状态。</p><p>SQL 的全称是：</p><blockquote><p> Structured Query Language，结构化查询语言。</p></blockquote><p>SQL 本身好说，我们小学时候都学习过了，无非就是 CRUD 嘛，而且我们还知道它是一种 <strong>语言</strong>，语言是一种存在于对象之间用于交流表达的 <strong>能力</strong>，例如跟中国人交流用汉语、跟英国人交流用英语、跟火星人交流用火星语、跟小猫交流用喵喵语、跟计算机交流我们用机器语言、跟数据库管理系统（DBMS）交流我们用 SQL。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_talk.png"></p><p>想必大家立马就能明白，想要与某个对象交流，必须拥有与此对象交流的语言能力才行！所以无论是技术人员、还是应用程序系统、或是某个高级语言环境，想要访问/操作数据库，都必须具备 SQL 这项能力；因此你能看到像 Java ，像 Python ，像 Go 等等这些高级语言环境中，都会嵌入（支持） SQL 能力，达到与数据库交互的目的。 </p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_connect.png"></p><p>很显然，能够学习 Mybatis 这么一门高精尖（ru-men）持久层框架的编程人群，对于 SQL 的编写能力肯定已经掌握得 ss 的，平时各种 SQL 编写那都是信手拈来的事， 只不过对于 <strong>动态SQL</strong> 到底是个什么东西，似乎还有一些朋友似懂非懂！但是没关系，我们百度一下。</p><blockquote><p>动态 SQL：一般指根据用户输入或外部条件 <strong>动态组合</strong> 的 SQL 语句块。</p></blockquote><p>很容易理解，随外部条件动态组合的 SQL 语句块！我们先针对动态 SQL 这个词来剖析，世间万物，有动态那就相对应的有静态，那么他们的边界在哪里呢？又该怎么区分呢？</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/dong_jing.jpg"></p><p>其实，上面我们已经介绍过，在例如 Java 高级语言中，都会嵌入（支持）SQL 能力，一般我们可以直接在代码或配置文件中编写 SQL 语句，如果一个 SQL 语句在 “编译阶段” 就已经能确定 <strong>主体结构</strong>，那我们称之为静态 SQL，如果一个 SQL 语句在编译阶段无法确定主体结构，需要等到程序真正 “运行时” 才能最终确定，那么我们称之为动态 SQL，举个例子：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 1、定义SQL --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;dao&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectAll&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;user&quot;</span>&gt;</span></span><br><span class="line">    select * from t_user</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2、执行SQL</span></span><br><span class="line">sqlSession.select(<span class="string">&quot;dao.selectAll&quot;</span>);</span><br></pre></td></tr></table></figure><p>很明显，以上这个 SQL ，在编译阶段我们都已经知道它的主体结构，即查询 t_user 表的所有记录，而无需等到程序运行时才确定这个主体结构，因此以上属于 <strong>静态 SQL</strong>。那我们再看看下面这个语句：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 1、定义SQL --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;dao&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectAll&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;user&quot;</span>&gt;</span></span><br><span class="line">    select * from t_user </span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;id != null&quot;</span>&gt;</span></span><br><span class="line">            where id = #&#123;id&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2、执行SQL</span></span><br><span class="line"><span class="type">User</span> <span class="variable">user1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">user1.setId(<span class="number">1</span>);</span><br><span class="line">sqlSession.select(<span class="string">&quot;dao.selectAll&quot;</span>,user1);  <span class="comment">// 有 id</span></span><br><span class="line"></span><br><span class="line"><span class="type">User</span> <span class="variable">user2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>(); </span><br><span class="line">sqlSession.select(<span class="string">&quot;dao.selectAll&quot;</span>,user2);  <span class="comment">// 无 id</span></span><br></pre></td></tr></table></figure><p>认真观察，以上这个 SQL 语句，额外添加了一块 <strong>if 标签</strong> 作为条件判断，所以应用程序在编译阶段是无法确定 SQL 语句最终主体结构的，只有在运行时根据应用程序是否传入 id 这个条件，来动态的拼接最终执行的 SQL 语句，因此属于动态 SQL 。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_process.png"></p><p>另外，还有一种常见的情况，大家看看下面这个 SQL 语句算是动态 SQL 语句吗？</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 1、定义SQL --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">&quot;dao&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectAll&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;user&quot;</span>&gt;</span></span><br><span class="line">    select * from t_user where id = #&#123;id&#125; </span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">mapper</span>&gt;</span></span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 2、执行SQL</span></span><br><span class="line"><span class="type">User</span> <span class="variable">user1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">User</span>();</span><br><span class="line">user1.setId(<span class="number">1</span>);</span><br><span class="line">sqlSession.select(<span class="string">&quot;dao.selectAll&quot;</span>,user1);  <span class="comment">// 有 id</span></span><br></pre></td></tr></table></figure><p>根据动态 SQL 的定义，大家是否能判断以上的语句块是否属于动态 SQL？</p><p><strong>答案：不属于动态 SQL ！</strong></p><p>原因很简单，这个 SQL 在编译阶段就已经明确主体结构了，虽然外部动态的传入一个 id ，可能是1，可能是2，可能是100，但是因为它的主体结构已经确定，这个语句就是查询一个指定 id 的用户记录，它最终执行的 SQL 语句不会有任何动态的变化，所以顶多算是一个支持动态传参的静态 SQL 。</p><p>至此，我们对于动态 SQL 和静态 SQL 的区别已经有了一个基础认知，但是有些好奇的朋友又会思考另一个问题：动态 SQL 是 Mybatis 独有的吗？</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/thinking.png"></p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/02.png"></p><h4 id="2、动态SQL的诞生记"><a href="#2、动态SQL的诞生记" class="headerlink" title="2、动态SQL的诞生记"></a>2、动态SQL的诞生记</h4><p>我们都知道，SQL 是一种伟大的数据库语言 <strong>标准</strong>，在数据库管理系统纷争的时代，它的出现统一规范了数据库操作语言，而此时，市面上的数据库管理软件百花齐放，我最早使用的 SQL Server 数据库，当时用的数据库管理工具是 SQL Server Management Studio，后来接触 Oracle 数据库，用了 PL/SQL Developer，再后来直至今日就几乎都在用 MySQL 数据库（这个跟各种云厂商崛起有关），所以基本使用 Navicat 作为数据库管理工具，当然如今市面上还有许多许多，数据库管理工具嘛，只要能便捷高效的管理我们的数据库，那就是好工具，duck 不必纠结选择哪一款！</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/dbphoto.jpg"></p><p>那这么多好工具，都提供什么功能呢？相信我们平时接触最多的就是接收执行 SQL 语句的输入界面（也称为查询编辑器），这个输入界面几乎支持所有 SQL 语法，例如我们编写一条语句查询 id 等于15 的用户数据记录：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> id <span class="operator">=</span> <span class="number">15</span> ;</span><br></pre></td></tr></table></figure><p>我们来看一下这个查询结果：</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/mysql_navicate.jpg"></p><p>很显然，在这个输入界面内输入的任何 SQL 语句，对于数据库管理工具来说，都是 <strong>动态 SQL</strong>！因为工具本身并不可能提前知道用户会输入什么 SQL 语句，只有当用户执行之后，工具才接收到用户实际输入的 SQL 语句，才能最终确定 SQL 语句的主体结构，当然！即使我们不通过可视化的数据库管理工具，也可以用数据库本身自带支持的命令行工具来执行 SQL 语句。但无论用户使用哪类工具，输入的语句都会被工具认为是 <strong>动态 SQL</strong>！</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/tools_sql.jpg"></p><p>这么一说，动态 SQL 原来不是 Mybatis 独有的特性！其实除了以上介绍的数据库管理工具以外，在纯 JDBC 时代，我们就经常通过字符串来动态的拼接 SQL 语句，这也是在高级语言环境（例如 Java 语言编程环境）中早期常用的动态 SQL 构建方式！</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 外部条件id</span></span><br><span class="line"><span class="type">Integer</span> <span class="variable">id</span> <span class="operator">=</span> Integer.valueOf(<span class="number">15</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 动态拼接SQL</span></span><br><span class="line"><span class="type">StringBuilder</span> <span class="variable">sql</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">StringBuilder</span>();</span><br><span class="line">sql.append(<span class="string">&quot; select  *   &quot;</span>);</span><br><span class="line">sql.append(<span class="string">&quot;   from user &quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据外部条件id动态拼接SQL</span></span><br><span class="line"><span class="keyword">if</span> ( <span class="literal">null</span> != id )&#123;</span><br><span class="line">    sql.append(<span class="string">&quot; where id = &quot;</span> + id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 执行语句</span></span><br><span class="line">connection.prepareStatement(sql);</span><br></pre></td></tr></table></figure><p>只不过，这种构建动态 SQL 的方式，存在很大的安全问题和异常风险（我们第5点会详细介绍），所以不建议使用，后来 Mybatis 入世之后，在对待动态 SQL 这件事上，就格外上心，它默默发誓，一定要为使用 Mybatis 框架的用户提供一套棒棒的方案（标签）来灵活构建动态 SQL！</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_dongtai.jpg"></p><p>于是乎，Mybatis 借助 OGNL 的表达式的伟大设计，可算在动态 SQL 构建方面提供了各类功能强大的辅助标签，我们简单列举一下有：if、choose、when、otherwise、trim、where、set、foreach、bind等，我随手翻了翻我电脑里头曾经保存的学习笔记，我们一起在第3节中温故知新，详细的讲一讲吧~</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_all.jpg"></p><p>另外，需要纠正一点，就是我们平日里在 Mybatis 框架中常说的动态 SQL ，其实特指的也就是 Mybatis 框架中的这一套动态 SQL <strong>标签</strong>，或者说是这一 <strong>特性</strong>，而并不是在说动态 SQL 本身。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/03.png"></p><h4 id="3、动态SQL标签的9大标签"><a href="#3、动态SQL标签的9大标签" class="headerlink" title="3、动态SQL标签的9大标签"></a>3、动态SQL标签的9大标签</h4><p>很好，可算进入我们动态 SQL 标签的主题，根据前面的铺垫，其实我们都能发现，很多时候静态 SQL 语句并不能满足我们复杂的业务场景需求，所以我们需要有适当灵活的一套方式或者能力，来便捷高效的构建动态 SQL 语句，去匹配我们动态变化的业务需求。举个栗子，在下面此类多条件的场景需求之下，动态 SQL 语句就显得尤为重要（先登场 if 标签）。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/es_sql.jpg"></p><p>当然，很多朋友会说这类需求，不能用 SQL 来查，得用搜索引擎，确实如此。但是呢，在我们的实际业务需求当中，还是存在很多没有引入搜索引擎系统，或者有些根本无需引入搜索引擎的应用程序或功能，它们也会涉及到多选项多条件或者多结果的业务需求，那此时也就确实需要使用动态 SQL 标签来灵活构建执行语句。</p><p>那么， Mybatis 目前都提供了哪些棒棒的动态 SQL 标签呢 ？我们先引出一个类叫做 XMLScriptBuilder ，大家先简单理解它是负责解析我们的动态 SQL 标签的这么一个构建器，在第4点底层原理中我们再详细介绍。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// XML脚本标签构建器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">XMLScriptBuilder</span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 标签节点处理器池</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, NodeHandler&gt; nodeHandlerMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 构造器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">XMLScriptBuilder</span><span class="params">()</span> &#123; </span><br><span class="line">        initNodeHandlerMap();</span><br><span class="line">        <span class="comment">//... 其它初始化不赘述也不重要</span></span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 初始化</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">initNodeHandlerMap</span><span class="params">()</span> &#123;</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;trim&quot;</span>, <span class="keyword">new</span> <span class="title class_">TrimHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;where&quot;</span>, <span class="keyword">new</span> <span class="title class_">WhereHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;set&quot;</span>, <span class="keyword">new</span> <span class="title class_">SetHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;foreach&quot;</span>, <span class="keyword">new</span> <span class="title class_">ForEachHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;if&quot;</span>, <span class="keyword">new</span> <span class="title class_">IfHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;choose&quot;</span>, <span class="keyword">new</span> <span class="title class_">ChooseHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;when&quot;</span>, <span class="keyword">new</span> <span class="title class_">IfHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;otherwise&quot;</span>, <span class="keyword">new</span> <span class="title class_">OtherwiseHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;bind&quot;</span>, <span class="keyword">new</span> <span class="title class_">BindHandler</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>其实源码中很清晰得体现，一共有 9 大动态 SQL 标签！Mybatis 在初始化解析配置文件的时候，会实例化这么一个标签节点的构造器，那么它本身就会提前把所有 Mybatis 支持的动态 SQL 标签对象对应的处理器给进行一个实例化，然后放到一个 Map 池子里头，而这些处理器，都是该类 XMLScriptBuilder 的一个匿名内部类，而匿名内部类的功能也很简单，就是解析处理对应类型的标签节点，在后续应用程序使用动态标签的时候，Mybatis 随时到 Map 池子中匹配对应的标签节点处理器，然后进解析即可。下面我们分别对这 9 大动态 SQL 标签进行介绍，排（gen）名（ju）不（wo）分（de）先（xi）后（hao）：</p><hr><h5 id="Top1、if-标签"><a href="#Top1、if-标签" class="headerlink" title="Top1、if 标签"></a>Top1、if 标签</h5><p>常用度：★★★★★</p><p>实用性：★★★★☆</p><p>if 标签，绝对算得上是一个伟大的标签，任何不支持流程控制（或语句控制）的应用程序，都是耍流氓，几乎都不具备现实意义，实际的应用场景和流程必然存在条件的控制与流转，而 if 标签在 <strong>单条件分支判断</strong> 应用场景中就起到了舍我其谁的作用，语法很简单，如果满足，则执行，不满足，则忽略/跳过。</p><ul><li>if 标签 ： 内嵌于 select / delete / update / insert 标签，如果满足 test 属性的条件，则执行代码块</li><li>test 属性 ：作为 if 标签的属性，用于条件判断，使用 OGNL 表达式。</li></ul><p>举个例子：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span>&gt;</span></span><br><span class="line">    select * from User where 1=1</span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; age != null &quot;</span>&gt;</span></span><br><span class="line">        and age &gt; #&#123;age&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; name != null &quot;</span>&gt;</span></span><br><span class="line">        and name like concat(#&#123;name&#125;,&#x27;%&#x27;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>很明显，if 标签元素常用于包含 where 子句的条件拼接，它相当于 Java 中的 if 语句，和 test 属性搭配使用，通过判断参数值来决定是否使用某个查询条件，也可用于 Update 语句中判断是否更新某个字段，或用于 Insert 语句中判断是否插入某个字段的值。</p><p>每一个 if 标签在进行单条件判断时，需要把判断条件设置在 test 属性中，这是一个常见的应用场景，我们常用的用户查询系统功能中，在前端一般提供很多可选的查询项，支持性别筛选、年龄区间筛查、姓名模糊匹配等，那么我们程序中接收用户输入之后，Mybatis 的动态 SQL 节省我们很多工作，允许我们在代码层面不进行参数逻辑处理和 SQL 拼接，而是把参数传入到 SQL 中进行条件判断动态处理，我们只需要把精力集中在 XML 的维护上，既灵活也方便维护，可读性还强。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_if.jpg"></p><p>有些心细的朋友可能就发现一个问题，为什么 where 语句会添加一个 1=1 呢？其实我们是为了方便拼接后面符合条件的 if 标签语句块，否则没有 1=1 的话我们拼接的 SQL 就会变成 select * from user where and age &gt; 0 , 显然这不是我们期望的结果，当然也不符合 SQL 的语法，数据库也不可能执行成功，所以我们投机取巧添加了 1=1 这个语句，但是始终觉得多余且没必要，Mybatis 也考虑到了，所以等会我们讲 where 标签，它是如何完美解决这个问题的。</p><blockquote><p>注意：if 标签作为单条件分支判断，只能控制与非此即彼的流程，例如以上的例子，如果年龄 age 和姓名 name 都不存在，那么系统会把所有结果都查询出来，但有些时候，我们希望系统更加灵活，能有更多的流程分支，例如像我们 Java 当中的 if else 或 switch case default，不仅仅只有一个条件分支，所以接下来我们介绍 choose 标签，它就能满足多分支判断的应用场景。</p></blockquote><hr><h5 id="Top2、choose-标签、when-标签、otherwise-标签"><a href="#Top2、choose-标签、when-标签、otherwise-标签" class="headerlink" title="Top2、choose 标签、when 标签、otherwise 标签"></a>Top2、choose 标签、when 标签、otherwise 标签</h5><p>常用度：★★★★☆</p><p>实用性：★★★★☆</p><p>有些时候，我们并不希望条件控制是非此即彼的，而是希望能提供多个条件并从中选择一个，所以贴心的 Mybatis  提供了 choose 标签元素，类似我们 Java 当中的 if else 或 switch case default，choose 标签必须搭配 when 标签和 otherwise 标签使用，验证条件依然是使用 test 属性进行验证。</p><ul><li>choose 标签：顶层的多分支标签，单独使用无意义</li><li>when 标签：内嵌于 choose 标签之中，当满足某个 when 条件时，执行对应的代码块，并终止跳出 choose 标签，choose 中必须至少存在一个 when 标签，否则无意义</li><li>otherwise 标签：内嵌于 choose 标签之中，当不满足所有 when 条件时，则执行 otherwise 代码块，choose 中 <strong>至多</strong> 存在一个 otherwise 标签，可以不存在该标签</li><li>test 属性 ：作为 when 与 otherwise 标签的属性，作为条件判断，使用 OGNL 表达式</li></ul><p>依据下面的例子，当应用程序输入年龄 age 或者姓名 name 时，会执行对应的 when 标签内的代码块，如果 when 标签的年龄 age 和姓名 name 都不满足，则会拼接 otherwise 标签内的代码块。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span>&gt;</span></span><br><span class="line">    select * from User where 1=1 </span><br><span class="line">    <span class="tag">&lt;<span class="name">choose</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot; age != null &quot;</span>&gt;</span></span><br><span class="line">        and age &gt; #&#123;age&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">when</span> <span class="attr">test</span>=<span class="string">&quot; name != null &quot;</span>&gt;</span></span><br><span class="line">        and name like concat(#&#123;name&#125;,&#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">when</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">otherwise</span>&gt;</span></span><br><span class="line">            and sex = &#x27;男&#x27;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">otherwise</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">choose</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_choose.jpg"></p><p>很明显，choose 标签作为多分支条件判断，提供了更多灵活的流程控制，同时 otherwise 的出现也为程序流程控制兜底，有时能够避免部分系统风险、过滤部分条件、避免当程序没有匹配到条件时，把整个数据库资源全部查询或更新。</p><blockquote><p>至于为何 choose 标签这么棒棒，而常用度还是比 if 标签少了一颗星呢？原因也简单，因为 choose 标签的很多使用场景可以直接用 if 标签代替。另外据我统计，if 标签在实际业务应用当中，也要多于 choose 标签，大家也可以具体核查自己的应用程序中动态 SQL 标签的占比情况，统计分析一下。 </p></blockquote><hr><h5 id="Top3、foreach-标签"><a href="#Top3、foreach-标签" class="headerlink" title="Top3、foreach 标签"></a>Top3、foreach 标签</h5><p>常用度：★★★☆☆</p><p>实用性：★★★★☆</p><p>有些场景，可能需要查询 id 在 1 ~ 100 的用户记录</p><p>有些场景，可能需要批量插入 100 条用户记录</p><p>有些场景，可能需要更新 500 个用户的姓名</p><p>有些场景，可能需要你删除 10 条用户记录</p><p><strong>请问大家</strong>：</p><p>很多增删改查场景，操作对象都是集合/列表</p><p>如果是你来设计支持 Mybatis 的这一类集合/列表遍历场景，你会提供什么能力的标签来辅助构建你的 SQL 语句从而去满足此类业务场景呢？</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_foreach1.jpg"></p><p>额(⊙o⊙)…</p><p>那如果一定要用 Mybatis 框架呢？</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_foreach2.jpg"></p><p>没错，确实 Mybatis 提供了 foreach 标签来处理这几类需要遍历集合的场景，foreach 标签作为一个循环语句，他能够很好的支持数组、Map、或实现了 Iterable 接口（List、Set）等，尤其是在构建 in 条件语句的时候，我们常规的用法都是 id in (1,2,3,4,5 … 100) ，理论上我们可以在程序代码中拼接字符串然后通过 ${ ids } 方式来传值获取，但是这种方式不能防止 SQL 注入风险，同时也特别容易拼接错误，所以我们此时就需要使用 #{} + foreach 标签来配合使用，以满足我们实际的业务需求。譬如我们传入一个 List 列表查询 id 在 1 ~ 100 的用户记录：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>&gt;</span></span><br><span class="line">    select  * from user where ids in </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span> </span></span><br><span class="line"><span class="tag">        <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">            #&#123;item&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>最终拼接完整的语句就变成： </p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">select</span>  <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> ids <span class="keyword">in</span> (<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>,...,<span class="number">100</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当然你也可以这样编写：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>&gt;</span></span><br><span class="line">    select  * from user where </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span> </span></span><br><span class="line"><span class="tag">        <span class="attr">open</span>=<span class="string">&quot; &quot;</span> <span class="attr">separator</span>=<span class="string">&quot; or &quot;</span> <span class="attr">close</span>=<span class="string">&quot; &quot;</span>&gt;</span></span><br><span class="line">            id = #&#123;item&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>最终拼接完整的语句就变成： </p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">select</span>  <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> id <span class="operator">=</span><span class="number">1</span> <span class="keyword">or</span> id <span class="operator">=</span><span class="number">2</span> <span class="keyword">or</span> id <span class="operator">=</span><span class="number">3</span>  ... <span class="keyword">or</span> id <span class="operator">=</span> <span class="number">100</span>;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>在数据量大的情况下这个性能会比较尴尬，这里仅仅做一个用法的举例。所以经过上面的举栗，相信大家也基本能猜出 foreach 标签元素的基本用法：</p><ul><li>foreach 标签：顶层的遍历标签，单独使用无意义</li><li>collection 属性：必填，Map 或者数组或者列表的属性名（不同类型的值获取下面会讲解）</li><li>item 属性：变量名，值为遍历的每一个值（可以是对象或基础类型），如果是对象那么依旧是 OGNL 表达式取值即可，例如 #{item.id} 、#{ user.name } 等</li><li>index 属性：索引的属性名，在遍历列表或数组时为当前索引值，当迭代的对象时 Map 类型时，该值为 Map 的键值（key）</li><li>open 属性：循环内容开头拼接的字符串，可以是空字符串</li><li>close 属性：循环内容结尾拼接的字符串，可以是空字符串</li><li>separator 属性：每次循环的分隔符</li></ul><p><strong>第一，当传入的参数为 List 对象时</strong>，系统会默认添加一个 key 为 ‘list’ 的值，把列表内容放到这个 key 为 list 的集合当中，在 foreach 标签中可以直接通过 collection=”list” 获取到 List 对象，无论你传入时使用 kkk 或者 aaa ,都无所谓，系统都会默认添加一个 key 为 list 的值，并且 item 指定遍历的对象值，index 指定遍历索引值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java 代码</span></span><br><span class="line"><span class="type">List</span> <span class="variable">kkk</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ArrayList</span>();</span><br><span class="line">kkk.add(<span class="number">1</span>);</span><br><span class="line">kkk.add(<span class="number">2</span>);</span><br><span class="line">...</span><br><span class="line">kkk.add(<span class="number">100</span>);</span><br><span class="line">sqlSession.selectList(<span class="string">&quot;findAll&quot;</span>,kkk);</span><br></pre></td></tr></table></figure><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- xml 配置 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>&gt;</span></span><br><span class="line">    select  * from user where ids in </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span> </span></span><br><span class="line"><span class="tag">        <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">            #&#123;item&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>第二，当传入的参数为数组时</strong>，系统会默认添加一个 key 为 ‘array’ 的值，把列表内容放到这个 key 为 array 的集合当中，在 foreach 标签中可以直接通过 collection=”array” 获取到数组对象，无论你传入时使用 ids 或者 aaa ,都无所谓，系统都会默认添加一个 key 为 array 的值，并且 item 指定遍历的对象值，index 指定遍历索引值。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java 代码</span></span><br><span class="line">String [] ids = <span class="keyword">new</span> <span class="title class_">String</span>[<span class="number">3</span>];</span><br><span class="line">ids[<span class="number">0</span>] = <span class="string">&quot;1&quot;</span>;</span><br><span class="line">ids[<span class="number">1</span>] = <span class="string">&quot;2&quot;</span>;</span><br><span class="line">ids[<span class="number">2</span>] = <span class="string">&quot;3&quot;</span>;</span><br><span class="line">sqlSession.selectList(<span class="string">&quot;findAll&quot;</span>,ids);</span><br></pre></td></tr></table></figure><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- xml 配置 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>&gt;</span></span><br><span class="line">    select  * from user where ids in </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;array&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">item</span>=<span class="string">&quot;item&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span> </span></span><br><span class="line"><span class="tag">        <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span>&gt;</span></span><br><span class="line">            #&#123;item&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>第三，当传入的参数为 Map 对象时</strong>，系统并 <strong>不会</strong> 默认添加一个 key 值，需要手工传入，例如传入 key 值为 map2 的集合对象，在 foreach 标签中可以直接通过 collection=”map2” 获取到 Map 对象，并且 item 代表每次迭代的的 value 值，index 代表每次迭代的 key 值。其中 item 和 index 的值名词可以随意定义，例如 item = “value111”，index =”key111”。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java 代码</span></span><br><span class="line"><span class="type">Map</span> <span class="variable">map2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">map2.put(<span class="string">&quot;k1&quot;</span>,<span class="number">1</span>);</span><br><span class="line">map2.put(<span class="string">&quot;k2&quot;</span>,<span class="number">2</span>);</span><br><span class="line">map2.put(<span class="string">&quot;k3&quot;</span>,<span class="number">3</span>);</span><br><span class="line"></span><br><span class="line"><span class="type">Map</span> <span class="variable">map1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">map1.put(<span class="string">&quot;map2&quot;</span>,map2);</span><br><span class="line">sqlSession.selectList(<span class="string">&quot;findAll&quot;</span>,map1);</span><br></pre></td></tr></table></figure><p>挺闹心，map1 套着 map2，才能在 foreach 的 collection 属性中获取到。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- xml 配置 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>&gt;</span></span><br><span class="line">    select  * from user where</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;map2&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">item</span>=<span class="string">&quot;value111&quot;</span> <span class="attr">index</span>=<span class="string">&quot;key111&quot;</span> </span></span><br><span class="line"><span class="tag">        <span class="attr">open</span>=<span class="string">&quot; &quot;</span> <span class="attr">separator</span>=<span class="string">&quot; or &quot;</span> <span class="attr">close</span>=<span class="string">&quot; &quot;</span>&gt;</span></span><br><span class="line">        id = #&#123;value111&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_foreach.jpg"></p><p>可能你会觉得 Map 受到不公平对待，为何 map 不能像 List 或者 Array 一样，在框架默认设置一个 ‘map’ 的 key 值呢？但其实不是不公平，而是我们在 Mybatis 框架中，所有传入的任何参数都会供上下文使用，于是参数会被统一放到一个内置参数池子里面，这个内置参数池子的数据结构是一个 map 集合，而这个 map 集合可以通过使用 “_parameter” 来获取，所有 key 都会存储在 _parameter 集合中，因此：</p><ul><li>当你传入的参数是一个 list 类型时，那么这个参数池子需要有一个 key 值，以供上下文获取这个 list 类型的对象，所以默认设置了一个 ‘list’ 字符串作为 key 值，获取时通过使用 _parameter.list 来获取，一般使用 list 即可。</li><li>同样的，当你传入的参数是一个 array 数组时，那么这个参数池子也会默认设置了一个 ‘array’ 字符串作为 key 值，以供上下文获取这个 array 数组的对象值，获取时通过使用 _parameter.array 来获取，一般使用 array 即可。</li><li>但是！当你传入的参数是一个 map 集合类型时，那么这个参数池就没必要为你添加默认 key 值了，因为 map 集合类型本身就会有很多 key 值，例如你想获取 map 参数的某个 key 值，你可以直接使用 _parameter.name 或者 _parameter.age 即可，就没必要还用 _parameter.map.name 或者 _parameter.map.age ，所以这就是 map 参数类型无需再构建一个 ‘map’ 字符串作为 key 的原因，对象类型也是如此，例如你传入一个 User 对象。</li></ul><p>因此，如果是 Map 集合，你可以这么使用：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// java 代码</span></span><br><span class="line"><span class="type">Map</span> <span class="variable">map2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line">map2.put(<span class="string">&quot;k1&quot;</span>,<span class="number">1</span>);</span><br><span class="line">map2.put(<span class="string">&quot;k2&quot;</span>,<span class="number">2</span>);</span><br><span class="line">map2.put(<span class="string">&quot;k3&quot;</span>,<span class="number">3</span>); </span><br><span class="line">sqlSession.selectList(<span class="string">&quot;findAll&quot;</span>,map2);</span><br></pre></td></tr></table></figure><p>直接使用 collection=”_parameter”，你会发现神奇的 key 和 value 都能通过 _parameter 遍历在 index 与 item 之中。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- xml 配置 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>&gt;</span></span><br><span class="line">    select  * from user where</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;_parameter&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">item</span>=<span class="string">&quot;value111&quot;</span> <span class="attr">index</span>=<span class="string">&quot;key111&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">open</span>=<span class="string">&quot; &quot;</span> <span class="attr">separator</span>=<span class="string">&quot; or &quot;</span> <span class="attr">close</span>=<span class="string">&quot; &quot;</span>&gt;</span></span><br><span class="line">        id = #&#123;value111&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/_paramter.jpg"></p><blockquote><p><strong>延伸</strong>：当传入参数为多个对象时，例如传入 User 和  Room 等，那么通过内置参数获取对象可以使用 _parameter.get(0).username，或者 _parameter.get(1).roomname 。假如你传入的参数是一个简单数据类型，例如传入 int =1 或者 String = ‘你好’，那么都可以直接使用 _parameter 代替获取值即可，这就是很多人会在动态  SQL 中直接使用 # { _parameter } 来获取简单数据类型的值。 </p></blockquote><p>那到这里，我们基本把 foreach 基本用法介绍完成，不过以上只是针对查询的使用场景，对于删除、更新、插入的用法，也是大同小异，我们简单说一下，如果你希望批量插入 100 条用户记录：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;insertUser&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;java.util.List&quot;</span>&gt;</span></span><br><span class="line">    insert into user(id,username) values</span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span> </span></span><br><span class="line"><span class="tag">         <span class="attr">item</span>=<span class="string">&quot;user&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">close</span>=<span class="string">&quot;;&quot;</span> &gt;</span></span><br><span class="line">        (#&#123;user.id&#125;,#&#123;user.username&#125;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如果你希望更新 500 个用户的姓名：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;java.util.List&quot;</span>&gt;</span></span><br><span class="line">    update user </span><br><span class="line">       set username = &#x27;潘潘&#x27; </span><br><span class="line">     where id in </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span></span></span><br><span class="line"><span class="tag">        <span class="attr">item</span>=<span class="string">&quot;user&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span> </span></span><br><span class="line"><span class="tag">        <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span> &gt;</span></span><br><span class="line">        #&#123;user.id&#125;    </span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如果你希望你删除 10 条用户记录：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">delete</span> <span class="attr">id</span>=<span class="string">&quot;deleteUser&quot;</span> <span class="attr">parameterType</span>=<span class="string">&quot;java.util.List&quot;</span>&gt;</span></span><br><span class="line">    delete from user  </span><br><span class="line">          where id in </span><br><span class="line">    <span class="tag">&lt;<span class="name">foreach</span> <span class="attr">collection</span>=<span class="string">&quot;list&quot;</span></span></span><br><span class="line"><span class="tag">         <span class="attr">item</span>=<span class="string">&quot;user&quot;</span> <span class="attr">index</span>=<span class="string">&quot;index&quot;</span> </span></span><br><span class="line"><span class="tag">         <span class="attr">separator</span>=<span class="string">&quot;,&quot;</span> <span class="attr">open</span>=<span class="string">&quot;(&quot;</span> <span class="attr">close</span>=<span class="string">&quot;)&quot;</span> &gt;</span></span><br><span class="line">        #&#123;user.id&#125;    </span><br><span class="line">    <span class="tag">&lt;/<span class="name">foreach</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">delete</span>&gt;</span></span><br></pre></td></tr></table></figure><p>更多玩法，期待你自己去挖掘！</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/foreach_empty.jpg"></p><blockquote><p> <strong>注意</strong>：使用 foreach 标签时，需要对传入的 collection 参数（List/Map/Set等）进行为空性判断，否则动态 SQL 会出现语法异常，例如你的查询语句可能是 select  * from user where ids in () ，导致以上语法异常就是传入参数为空，解决方案可以用 if 标签或 choose 标签进行为空性判断处理，或者直接在 Java 代码中进行逻辑处理即可，例如判断为空则不执行 SQL 。</p></blockquote><hr><h5 id="Top4、where-标签、set-标签"><a href="#Top4、where-标签、set-标签" class="headerlink" title="Top4、where 标签、set 标签"></a>Top4、where 标签、set 标签</h5><p>常用度：★★☆☆☆</p><p>实用性：★★★★☆</p><p>我们把 where 标签和 set 标签放置一起讲解，一是这两个标签在实际应用开发中常用度确实不分伯仲，二是这两个标签出自一家，都继承了 trim 标签，放置一起方便我们比对追根。（其中底层原理会在第4部分详细讲解）</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/where_set_trim.jpg"></p><p>之前我们介绍 if 标签的时候，相信大家都已经看到，我们在 where 子句后面拼接了 1=1 的条件语句块，目的是为了保证后续条件能够正确拼接，以前在程序代码中使用字符串拼接 SQL 条件语句常常如此使用，但是确实此种方式不够体面，也显得我们不高级。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span>&gt;</span></span><br><span class="line">    select * from User where 1=1</span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; age != null &quot;</span>&gt;</span></span><br><span class="line">        and age &gt; #&#123;age&#125;</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; name != null &quot;</span>&gt;</span></span><br><span class="line">        and name like concat(#&#123;name&#125;,&#x27;%&#x27;)</span><br><span class="line">    <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>以上是我们使用 1=1 的写法，那 where 标签诞生之后，是怎么巧妙处理后续的条件语句的呢？</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span>&gt;</span></span><br><span class="line">    select * from User </span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; age != null &quot;</span>&gt;</span></span><br><span class="line">            and age &gt; #&#123;age&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; name != null &quot;</span>&gt;</span></span><br><span class="line">            and name like concat(#&#123;name&#125;,&#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>我们只需把 where 关键词以及 1=1 改为 &lt; where &gt; 标签即可，另外还有一个特殊的处理能力，就是 where 标签能够智能的去除（忽略）首个满足条件语句的前缀，例如以上条件如果 age 和 name 都满足，那么 age 前缀 and 会被智能去除掉，无论你是使用 and 运算符或是 or 运算符，Mybatis 框架都会帮你智能处理。</p><p><strong>用法特别简单，我们用官术总结一下</strong>：</p><ul><li>where 标签：顶层的遍历标签，需要配合 if 标签使用，单独使用无意义，并且只会在子元素（如 if 标签）返回任何内容的情况下才插入 WHERE 子句。另外，若子句的开头为 “AND” 或 “OR”，where 标签也会将它替换去除。</li></ul><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_where_1.jpg"></p><p>了解了基本用法之后，我们再看看刚刚我们的例子中：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span>&gt;</span></span><br><span class="line">    select * from User </span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; age != null &quot;</span>&gt;</span></span><br><span class="line">            and age &gt; #&#123;age&#125;</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; name != null &quot;</span>&gt;</span></span><br><span class="line">            and name like concat(#&#123;name&#125;,&#x27;%&#x27;)</span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>如果 age 传入有效值 10 ，满足 age != null 的条件之后，那么就会返回 where 标签并去除首个子句运算符 and，最终的 SQL 语句会变成：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">User</span> <span class="keyword">where</span> age <span class="operator">&gt;</span> <span class="number">10</span>; </span><br><span class="line"><span class="comment">-- and 巧妙的不见了</span></span><br></pre></td></tr></table></figure><p>值得注意的是，where 标签 <strong>只会</strong> 智能的去除（忽略）首个满足条件语句的前缀，所以就建议我们在使用 where 标签的时候，每个语句都最好写上 and 前缀或者 or 前缀，否则像以下写法就很有可能出大事：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findUser&quot;</span>&gt;</span></span><br><span class="line">    select * from User </span><br><span class="line">    <span class="tag">&lt;<span class="name">where</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; age != null &quot;</span>&gt;</span></span><br><span class="line">             age &gt; #&#123;age&#125; </span><br><span class="line">             <span class="comment">&lt;!-- age 前缀没有运算符--&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot; name != null &quot;</span>&gt;</span></span><br><span class="line">             name like concat(#&#123;name&#125;,&#x27;%&#x27;)</span><br><span class="line">             <span class="comment">&lt;!-- name 前缀也没有运算符--&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">where</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>当 age 传入 10，name 传入 ‘潘潘’ 时，最终的 SQL 语句是：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">User</span> </span><br><span class="line"><span class="keyword">where</span> </span><br><span class="line">age <span class="operator">&gt;</span> <span class="number">10</span> </span><br><span class="line">name <span class="keyword">like</span> concat(<span class="string">&#x27;潘%&#x27;</span>)</span><br><span class="line"><span class="comment">-- 所有条件都没有and或or运算符</span></span><br><span class="line"><span class="comment">-- 这让age和name显得很尴尬~</span></span><br></pre></td></tr></table></figure><p>由于 name 前缀没有写 and 或 or 连接符，而 where 标签又不会智能的去除（忽略）<strong>非首个</strong> 满足条件语句的前缀，所以当  age 条件语句与 name 条件语句同时成立时，就会导致语法错误，这个需要谨慎使用，格外注意！原则上每个条件子句都建议在句首添加运算符 and 或 or ,首个条件语句可添加可不加。</p><blockquote><p>另外还有一个值得注意的点，我们使用 XML 方式配置 SQL 时，如果在 where 标签之后添加了注释，那么当有子元素满足条件时，除了 &lt; !– –&gt; 注释会被 where 忽略解析以外，其它注释例如 // 或 /**/ 或 – 等都会被 where 当成首个子句元素处理，导致后续真正的首个 AND 子句元素或 OR 子句元素没能被成功替换掉前缀，从而引起语法错误！</p></blockquote><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_where_2.jpg"></p><p>基于 where 标签元素的讲解，有助于我们快速理解 set 标签元素，毕竟它俩是如此相像。我们回忆一下以往我们的更新 SQL 语句：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">    update user </span><br><span class="line">       set age = #&#123;age&#125;,</span><br><span class="line">           username = #&#123;username&#125;,</span><br><span class="line">           password = #&#123;password&#125; </span><br><span class="line">     where id =#&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span> </span><br></pre></td></tr></table></figure><p>以上语句是我们日常用于更新指定 id 对象的 age 字段、 username 字段以及 password 字段，但是很多时候，我们可能只希望更新对象的某些字段，而不是每次都更新对象的所有字段，这就使得我们在语句结构的构建上显得惨白无力。于是有了 set 标签元素。</p><p><strong>用法与 where 标签元素相似</strong>：</p><ul><li>set 标签：顶层的遍历标签，需要配合 if 标签使用，单独使用无意义，并且只会在子元素（如 if 标签）返回任何内容的情况下才插入 set 子句。另外，若子句的 <strong>开头或结尾</strong> 都存在逗号 “,”  则 set 标签都会将它替换去除。</li></ul><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_set.jpg"></p><p>根据此用法我们可以把以上的例子改为：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">    update user </span><br><span class="line">        <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age !=null&quot;</span>&gt;</span></span><br><span class="line">               age = #&#123;age&#125;,</span><br><span class="line">           <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username !=null&quot;</span>&gt;</span></span><br><span class="line">              username = #&#123;username&#125;,</span><br><span class="line">           <span class="tag">&lt;/<span class="name">if</span>&gt;</span> </span><br><span class="line">           <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;password !=null&quot;</span>&gt;</span></span><br><span class="line">              password = #&#123;password&#125;,</span><br><span class="line">           <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">set</span>&gt;</span>    </span><br><span class="line">     where id =#&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span> </span><br></pre></td></tr></table></figure><p>很简单易懂，set 标签会智能拼接更新字段，以上例子如果传入 age =10 和 username = ‘潘潘’ ，则有两个字段满足更新条件，于是 set 标签会智能拼接 “ age = 10 ,” 和 “username =  ‘潘潘’ ,” 。其中由于后一个 username 属于最后一个子句，所以末尾逗号会被智能去除，最终的 SQL 语句是：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> <span class="keyword">user</span> <span class="keyword">set</span> age <span class="operator">=</span> <span class="number">10</span>,username <span class="operator">=</span>  <span class="string">&#x27;潘潘&#x27;</span> </span><br></pre></td></tr></table></figure><p>另外需要注意，set 标签下需要保证至少有一个条件满足，否则依然会产生语法错误，例如在无子句条件满足的场景下，最终的 SQL 语句会是这样：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">update</span> <span class="keyword">user</span> ;  ( oh<span class="operator">~</span> <span class="keyword">no</span><span class="operator">!</span>)</span><br></pre></td></tr></table></figure><p>既不会添加 set 标签，也没有子句更新字段，于是语法出现了错误，所以类似这类情况，一般需要在应用程序中进行逻辑处理，判断是否存在至少一个参数，否则不执行更新 SQL 。所以原则上要求 set 标签下至少存在一个条件满足，同时每个条件子句都建议在句末添加逗号 ,最后一个条件语句可加可不加。<strong>或者</strong> 每个条件子句都在句首添加逗号 ,第一个条件语句可加可不加，例如：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">update</span> <span class="attr">id</span>=<span class="string">&quot;updateUser&quot;</span>&gt;</span></span><br><span class="line">    update user </span><br><span class="line">        <span class="tag">&lt;<span class="name">set</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;age !=null&quot;</span>&gt;</span></span><br><span class="line">               ,age = #&#123;age&#125;</span><br><span class="line">           <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">           <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;username !=null&quot;</span>&gt;</span></span><br><span class="line">              ,username = #&#123;username&#125;</span><br><span class="line">           <span class="tag">&lt;/<span class="name">if</span>&gt;</span> </span><br><span class="line">           <span class="tag">&lt;<span class="name">if</span> <span class="attr">test</span>=<span class="string">&quot;password !=null&quot;</span>&gt;</span></span><br><span class="line">              ,password = #&#123;password&#125;</span><br><span class="line">           <span class="tag">&lt;/<span class="name">if</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;/<span class="name">set</span>&gt;</span>    </span><br><span class="line">     where id =#&#123;id&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">update</span>&gt;</span> </span><br></pre></td></tr></table></figure><blockquote><p>与 where 标签相同，我们使用 XML 方式配置 SQL 时，如果在 set 标签子句末尾添加了注释，那么当有子元素满足条件时，除了 &lt; !– –&gt; 注释会被 set 忽略解析以外，其它注释例如 // 或 /**/ 或 – 等都会被 set 标签当成末尾子句元素处理，导致后续真正的末尾子句元素的逗号没能被成功替换掉后缀，从而引起语法错误！</p></blockquote><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_set_2.jpg"></p><p>到此，我们的 where 标签元素与 set 标签就基本介绍完成，它俩确实极为相似，区别仅在于：</p><ul><li>where 标签插入前缀 where</li><li>set 标签插入前缀 set</li><li>where 标签仅智能替换前缀 AND 或 OR</li><li>set 标签可以只能替换前缀逗号，或后缀逗号，</li></ul><p>而这两者的前后缀去除策略，都源自于 trim 标签的设计，我们一起看看到底 trim 标签是有多灵活！</p><hr><h5 id="Top5、trim-标签"><a href="#Top5、trim-标签" class="headerlink" title="Top5、trim 标签"></a>Top5、trim 标签</h5><p>常用度：★☆☆☆☆</p><p>实用性：★☆☆☆☆</p><p>上面我们介绍了 where 标签与 set 标签，它俩的共同点无非就是前置关键词 where 或 set 的插入，以及前后缀符号（例如 AND | OR | ，）的智能去除。基于 where 标签和 set 标签本身都继承了 trim 标签，所以 trim 标签的大致实现我们也能猜出个一二三。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/trim_set_where.jpg"></p><p>其实 where 标签和 set 标签都只是 trim 标签的某种实现方案，trim 标签底层是通过 TrimSqlNode 类来实现的，它有几个关键属性：</p><ul><li><strong>prefix</strong> ：前缀，当 trim 元素内存在内容时，会给内容插入指定前缀</li><li><strong>suffix</strong> ：后缀，当 trim 元素内存在内容时，会给内容插入指定后缀</li><li><strong>prefixesToOverride</strong> ：前缀去除，支持多个，当 trim 元素内存在内容时，会把内容中匹配的前缀字符串去除。</li><li><strong>suffixesToOverride</strong> ：后缀去除，支持多个，当 trim 元素内存在内容时，会把内容中匹配的后缀字符串去除。</li></ul><p>所以 where 标签如果通过 trim 标签实现的话可以这么编写：（</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!--</span></span><br><span class="line"><span class="comment">  注意在使用 trim 标签实现 where 标签能力时</span></span><br><span class="line"><span class="comment">  必须在 AND 和 OR 之后添加空格</span></span><br><span class="line"><span class="comment">  避免匹配到 android、order 等单词 </span></span><br><span class="line"><span class="comment">--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">trim</span> <span class="attr">prefix</span>=<span class="string">&quot;WHERE&quot;</span> <span class="attr">prefixOverrides</span>=<span class="string">&quot;AND | OR&quot;</span> &gt;</span></span><br><span class="line">    ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">trim</span>&gt;</span></span><br></pre></td></tr></table></figure><p>而 set 标签如果通过 trim 标签实现的话可以这么编写：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">trim</span> <span class="attr">prefix</span>=<span class="string">&quot;SET&quot;</span> <span class="attr">prefixOverrides</span>=<span class="string">&quot;,&quot;</span> &gt;</span></span><br><span class="line">    ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">trim</span>&gt;</span></span><br><span class="line"></span><br><span class="line">或者</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">trim</span> <span class="attr">prefix</span>=<span class="string">&quot;SET&quot;</span> <span class="attr">suffixesToOverride</span>=<span class="string">&quot;,&quot;</span> &gt;</span></span><br><span class="line">    ...</span><br><span class="line"><span class="tag">&lt;/<span class="name">trim</span>&gt;</span></span><br></pre></td></tr></table></figure><p>所以可见 trim 是足够灵活的，不过由于 where 标签和 set 标签这两种 trim 标签变种方案已经足以满足我们实际开发需求，所以直接使用 trim 标签的场景实际上不太很多（其实是我自己使用的不多，基本没用过）。</p><blockquote><p>注意，set 标签之所以能够支持去除前缀逗号或者后缀逗号，是由于其在构造 trim 标签的时候进行了前缀后缀的去除设置，而 where 标签在构造 trim 标签的时候就仅仅设置了前缀去除。</p></blockquote><p>set  标签元素之构造时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Set 标签</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SetSqlNode</span> <span class="keyword">extends</span> <span class="title class_">TrimSqlNode</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> List&lt;String&gt; COMMA = Collections.singletonList(<span class="string">&quot;,&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 明显使用了前缀后缀去除，注意前后缀参数都传入了 COMMA </span></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">SetSqlNode</span><span class="params">(Configuration configuration,SqlNode contents)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(configuration, contents, <span class="string">&quot;SET&quot;</span>, COMMA, <span class="literal">null</span>, COMMA);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>where 标签元素之构造时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Where 标签</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">WhereSqlNode</span> <span class="keyword">extends</span> <span class="title class_">TrimSqlNode</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 其实包含了很多种场景</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">static</span> List&lt;String&gt; prefixList = Arrays.asList(<span class="string">&quot;AND &quot;</span>,<span class="string">&quot;OR &quot;</span>,<span class="string">&quot;AND\n&quot;</span>, <span class="string">&quot;OR\n&quot;</span>, <span class="string">&quot;AND\r&quot;</span>, <span class="string">&quot;OR\r&quot;</span>, <span class="string">&quot;AND\t&quot;</span>, <span class="string">&quot;OR\t&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 明显只使用了前缀去除，注意前缀传入 prefixList，后缀传入 null </span></span><br><span class="line">  <span class="keyword">public</span> <span class="title function_">WhereSqlNode</span><span class="params">(Configuration configuration, SqlNode contents)</span> &#123;</span><br><span class="line">    <span class="built_in">super</span>(configuration, contents, <span class="string">&quot;WHERE&quot;</span>, prefixList, <span class="literal">null</span>, <span class="literal">null</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h5 id="Top6、bind-标签"><a href="#Top6、bind-标签" class="headerlink" title="Top6、bind 标签"></a>Top6、bind 标签</h5><p>常用度：☆☆☆☆☆</p><p>实用性：★☆☆☆☆</p><p>简单来说，这个标签就是可以创建一个变量，并绑定到上下文，即供上下文使用，就是这样，我把官网的例子直接拷贝过来：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selecUser&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">bind</span> <span class="attr">name</span>=<span class="string">&quot;myName&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&#x27;%&#x27; + _parameter.getName() + &#x27;%&#x27;&quot;</span> /&gt;</span></span><br><span class="line">  SELECT * FROM user</span><br><span class="line">  WHERE name LIKE #&#123;myName&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>大家应该大致能知道以上例子的功效，其实就是辅助构建模糊查询的语句拼接，那有人就好奇了，为啥不直接拼接语句就行了，为什么还要搞出一个变量，绕一圈呢？</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/kaokaoyou.jpg"></p><p><strong>我先问一个问题</strong>：平时你使用 mysql 都是如何拼接模糊查询 like 语句的？</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> name <span class="keyword">like</span> concat(<span class="string">&#x27;%&#x27;</span>,#&#123;name&#125;,<span class="string">&#x27;%&#x27;</span>)</span><br></pre></td></tr></table></figure><p>确实如此，但如果有一天领导跟你说数据库换成 oracle 了，怎么办？上面的语句还能用吗？明显用不了，不能这么写，因为 oracle 虽然也有 concat 函数，但是只支持连接两个字符串，例如你最多这么写：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> name <span class="keyword">like</span> concat(<span class="string">&#x27;%&#x27;</span>,#&#123;name&#125;)</span><br></pre></td></tr></table></figure><p>但是少了右边的井号符号，所以达不到你预期的效果，于是你改成这样：</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> name <span class="keyword">like</span> <span class="string">&#x27;%&#x27;</span><span class="operator">||</span>#&#123;name&#125;<span class="operator">||</span><span class="string">&#x27;%&#x27;</span></span><br></pre></td></tr></table></figure><p>确实可以了，但是过几天领导又跟你说，数据库换回 mysql 了？额… 那不好意思，你又得把相关使用到模糊查询的地方改回来。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">select</span> <span class="operator">*</span> <span class="keyword">from</span> <span class="keyword">user</span> <span class="keyword">where</span> name <span class="keyword">like</span> concat(<span class="string">&#x27;%&#x27;</span>,#&#123;name&#125;,<span class="string">&#x27;%&#x27;</span>)</span><br></pre></td></tr></table></figure><p>很显然，数据库只要发生变更你的 sql 语句就得跟着改，特别麻烦，所以才有了一开始我们介绍 bind 标签官网的这个例子，无论使用哪种数据库，这个模糊查询的 Like 语法都是支持的：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selecUser&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">bind</span> <span class="attr">name</span>=<span class="string">&quot;myName&quot;</span> <span class="attr">value</span>=<span class="string">&quot;&#x27;%&#x27; + _parameter.getName() + &#x27;%&#x27;&quot;</span> /&gt;</span></span><br><span class="line">  SELECT * FROM user</span><br><span class="line">  WHERE name LIKE #&#123;myName&#125;</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个 bind 的用法，实打实解决了数据库重新选型后导致的一些问题，当然在实际工作中发生的概率不会太大，所以 bind 的使用我个人确实也使用的不多，可能还有其它一些应用场景，希望有人能发现之后来跟我们分享一下，总之我勉强给了一颗星（虽然没太多实际用处，但毕竟要给点面子）。</p><hr><h5 id="拓展：sql标签-include-标签"><a href="#拓展：sql标签-include-标签" class="headerlink" title="拓展：sql标签 + include 标签"></a>拓展：sql标签 + include 标签</h5><p>常用度：★★★☆☆</p><p>实用性：★★★☆☆</p><p>sql 标签与 include 标签组合使用，用于 SQL 语句的复用，日常高频或公用使用的语句块可以抽取出来进行复用，其实我们应该不陌生，早期我们学习 JSP 的时候，就有一个 include 标记可以引入一些公用可复用的页面文件，例如页面头部或尾部页面代码元素，这种复用的设计很常见。</p><p><strong>严格意义上 sql 、include 不算在动态 SQL 标签成员之内</strong>，只因它确实是宝藏般的存在，所以我要简单说说，sql 标签用于定义一段可重用的 SQL 语句片段，以便在其它语句中使用，而 include 标签则通过属性 refid 来引用对应 id 匹配的 sql 标签语句片段。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_include_1.jpg"></p><p>简单的复用代码块可以是：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 可复用的字段语句块 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">sql</span> <span class="attr">id</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span></span><br><span class="line">    id,username,password </span><br><span class="line"><span class="tag">&lt;/<span class="name">sql</span>&gt;</span></span><br></pre></td></tr></table></figure><p>查询或插入时简单复用：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 查询时简单复用 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;map&quot;</span>&gt;</span></span><br><span class="line">  select</span><br><span class="line">    <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">include</span>&gt;</span> </span><br><span class="line">  from user </span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 插入时简单复用 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">insert</span> <span class="attr">id</span>=<span class="string">&quot;insertUser&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;map&quot;</span>&gt;</span></span><br><span class="line">  insert into user(</span><br><span class="line">    <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">include</span>&gt;</span> </span><br><span class="line">  )values(</span><br><span class="line">    #&#123;id&#125;,#&#123;username&#125;,#&#123;password&#125; </span><br><span class="line">  )  </span><br><span class="line"><span class="tag">&lt;/<span class="name">insert</span>&gt;</span></span><br></pre></td></tr></table></figure><p>当然，复用语句还支持属性传递，例如：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 可复用的字段语句块 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">sql</span> <span class="attr">id</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span></span><br><span class="line">    $&#123;pojo&#125;.id,$&#123;pojo&#125;.username </span><br><span class="line"><span class="tag">&lt;/<span class="name">sql</span>&gt;</span></span><br></pre></td></tr></table></figure><p>这个 SQL 片段可以在其它语句中使用：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 查询时复用 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;selectUsers&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;map&quot;</span>&gt;</span></span><br><span class="line">  select</span><br><span class="line">    <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;pojo&quot;</span> <span class="attr">value</span>=<span class="string">&quot;u1&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">include</span>&gt;</span>,</span><br><span class="line">    <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;userColumns&quot;</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;pojo&quot;</span> <span class="attr">value</span>=<span class="string">&quot;u2&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line">  from user u1 cross join user u2</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>也可以在 include 元素的 refid 属性或多层内部语句中使用属性值，属性可以穿透传递，例如：</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_include_2.jpg"></p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- 简单语句块 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">sql</span> <span class="attr">id</span>=<span class="string">&quot;sql1&quot;</span>&gt;</span></span><br><span class="line">  $&#123;prefix&#125;_user</span><br><span class="line"><span class="tag">&lt;/<span class="name">sql</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 嵌套语句块 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">sql</span> <span class="attr">id</span>=<span class="string">&quot;sql2&quot;</span>&gt;</span></span><br><span class="line">  from</span><br><span class="line">    <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;$&#123;include_target&#125;&quot;</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">sql</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 查询时引用嵌套语句块 --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;select&quot;</span> <span class="attr">resultType</span>=<span class="string">&quot;map&quot;</span>&gt;</span></span><br><span class="line">  select</span><br><span class="line">    id, username</span><br><span class="line">  <span class="tag">&lt;<span class="name">include</span> <span class="attr">refid</span>=<span class="string">&quot;sql2&quot;</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;prefix&quot;</span> <span class="attr">value</span>=<span class="string">&quot;t&quot;</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">property</span> <span class="attr">name</span>=<span class="string">&quot;include_target&quot;</span> <span class="attr">value</span>=<span class="string">&quot;sql1&quot;</span>/&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="name">include</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>至此，关于 9 大动态 SQL 标签的基本用法我们已介绍完毕，另外我们还有一些疑问：Mybatis 底层是如何解析这些动态 SQL 标签的呢？最终又是怎么构建完整可执行的 SQL 语句的呢？带着这些疑问，我们在第4节中详细分析。 </p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_how.jpg"></p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/04.png"></p><h4 id="4、动态SQL的底层原理"><a href="#4、动态SQL的底层原理" class="headerlink" title="4、动态SQL的底层原理"></a>4、动态SQL的底层原理</h4><p>想了解 Mybatis 究竟是如何解析与构建动态 SQL ？首先推荐的当然是读源码，而读源码，是一个技术钻研问题，为了借鉴学习，为了工作储备，为了解决问题，为了让自己在编程的道路上跑得明白一些… 而希望通过读源码，去了解底层实现原理，切记不能脱离了整体去读局部，否则你了解到的必然局限且片面，从而轻忽了真核上的设计。如同我们读史或者观宇宙一样，最好的办法都是从整体到局部，不断放大，前后延展，会很舒服通透。所以我准备从 Mybatis 框架的核心主线上去逐步放大剖析。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/yuanma_line.jpg"></p><p>通过前面几篇文章的介绍（建议阅读 Mybatis 系列全解之六:《<strong>Mybatis 最硬核的 API 你知道几个？</strong>》），其实我们知道了 Mybatis 框架的核心部分在于构件的构建过程，从而支撑了外部应用程序的使用，从应用程序端创建配置并调用 API 开始，到框架端加载配置并初始化构件，再创建会话并接收请求，然后处理请求，最终返回处理结果等。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/Mybatis_key.jpg"></p><p>我们的动态 SQL 解析部分就发生在 SQL 语句对象 MappedStatement 构建时（<strong>上左高亮橘色</strong>部分，注意观察其中 SQL 语句对象与 SqlSource 、 BoundSql 的关系，在动态 SQL 解析流程特别关键）。我们再拉近一点，可以看到无论是使用 XML 配置 SQL 语句或是使用注解方式配置 SQL 语句，框架最终都会把解析完成的 SQL 语句对象存放到 MappedStatement 语句集合池子。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/MappedStatement_key.jpg"></p><p>而以上<strong>虚线高亮</strong>部分，即是 XML 配置方式解析过程与注解配置方式解析过程中涉及到动态 SQL 标签解析的流程，我们分别讲解：</p><ul><li><strong>第一，XML 方式配置 SQL 语句，框架如何解析？</strong></li></ul><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/xml_dysql.jpg"></p><p>以上为 XML 配置方式的 SQL 语句解析过程，无论是单独使用 Mybatis 框架还是集成 Spring 与 Mybatis 框架，程序启动入口都会首先从 SqlSessionFactoryBuilder.build() 开始构建，依次通过 XMLConfigBuilder 构建全局配置 Configuration 对象、通过 XMLMapperBuilder 构建每一个 Mapper 映射器、通过 XMLStatementBuilder 构建映射器中的每一个 SQL 语句对象（select/insert/update/delete）。而就在解析构建每一个 SQL 语句对象时，涉及到一个关键的方法 parseStatementNode（），即上图<strong>橘红色高亮</strong>部分，此方法内部就出现了一个处理动态 SQL 的核心节点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// XML配置语句构建器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">XMLStatementBuilder</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 实际解析每一个 SQL 语句</span></span><br><span class="line">    <span class="comment">// 例如 select|insert|update|delete</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">parseStatementNode</span><span class="params">()</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// [忽略]参数构建...</span></span><br><span class="line">        <span class="comment">// [忽略]缓存构建..</span></span><br><span class="line">        <span class="comment">// [忽略]结果集构建等等.. </span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 【重点】此处即是处理动态 SQL 的核心！！！</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">lang</span> <span class="operator">=</span> context.getStringAttribute(<span class="string">&quot;lang&quot;</span>);</span><br><span class="line">    <span class="type">LanguageDriver</span> <span class="variable">langDriver</span> <span class="operator">=</span> getLanguageDriver(lang);</span><br><span class="line">        <span class="type">SqlSource</span> <span class="variable">sqlSource</span> <span class="operator">=</span> langDriver.createSqlSource(..);</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// [忽略]最后把解析完成的语句对象添加进语句集合池</span></span><br><span class="line">        builderAssistant.addMappedStatement(语句对象)</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 大家先重点关注一下这段代码，其中【重点】部分的 LanguageDriver 与 SqlSource 会是我们接下来讲解动态 SQL 语句解析的核心类，我们不着急剖析，我们先把注解方式流程也梳理对比一下。</p><ul><li><strong>第二，注解方式配置 SQL 语句，框架如何解析？</strong></li></ul><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/anno_dysql.jpg"></p><p>大家会发现注解配置方式的 SQL 语句解析过程，与 XML 方式极为相像，唯一不同点就在于解析注解 SQL 语句时，使用了 MapperAnnotationBuilder  构建器，其中关于每一个语句对象 (@Select,@Insert,@Update,@Delete等) 的解析，又都会通过一个关键解析方法 parseStatement（），即上图<strong>橘红色高亮</strong>部分，此方法内部同样的出现了一个处理动态 SQL 的核心节点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注解配置语句构建器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MapperAnnotationBuilder</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 实际解析每一个 SQL 语句</span></span><br><span class="line">    <span class="comment">// 例如 @Select,@Insert,@Update,@Delete</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">parseStatement</span><span class="params">(Method method)</span> &#123;  </span><br><span class="line">        </span><br><span class="line">        <span class="comment">// [忽略]参数构建...</span></span><br><span class="line">        <span class="comment">// [忽略]缓存构建..</span></span><br><span class="line">        <span class="comment">// [忽略]结果集构建等等.. </span></span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 【重点】此处即是处理动态 SQL 的核心！！！</span></span><br><span class="line">    <span class="keyword">final</span> <span class="type">LanguageDriver</span> <span class="variable">languageDriver</span> <span class="operator">=</span> getLanguageDriver(method);  </span><br><span class="line">    <span class="keyword">final</span> <span class="type">SqlSource</span> <span class="variable">sqlSource</span> <span class="operator">=</span> buildSqlSource( languageDriver,... );</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// [忽略]最后把解析完成的语句对象添加进语句集合池</span></span><br><span class="line">        builderAssistant.addMappedStatement(语句对象)</span><br><span class="line"></span><br><span class="line">    &#125;    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>由此可见，不管是通过 XML 配置语句还是注解方式配置语句，构建流程都是 <strong>大致相同</strong>，并且依然出现了我们在 XML 配置方式中涉及到的语言驱动 LanguageDriver 与语句源 SqlSource ，那这两个类/接口到底为何物，为何能让 SQL 语句解析者都如此绕不开 ？</p><p>这一切，得从你编写的 SQL 开始讲起 …</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/mappedStatement_zone.jpg"></p><p>我们知道，无论 XML 还是注解，最终你的所有 SQL 语句对象都会被齐齐整整的解析完放置在 SQL 语句对象集合池中，以供执行器 Executor 具体执行增删改查 ( CRUD ) 时使用。而我们知道每一个 SQL 语句对象的属性，特别复杂繁多，例如超时设置、缓存、语句类型、结果集映射关系等等。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SQL 语句对象</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="keyword">class</span> <span class="title class_">MappedStatement</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> String resource;</span><br><span class="line">  <span class="keyword">private</span> Configuration configuration;</span><br><span class="line">  <span class="keyword">private</span> String id;</span><br><span class="line">  <span class="keyword">private</span> Integer fetchSize;</span><br><span class="line">  <span class="keyword">private</span> Integer timeout;</span><br><span class="line">  <span class="keyword">private</span> StatementType statementType;</span><br><span class="line">  <span class="keyword">private</span> ResultSetType resultSetType;</span><br><span class="line">    </span><br><span class="line">  <span class="comment">// SQL 源</span></span><br><span class="line">  <span class="keyword">private</span> SqlSource sqlSource;</span><br><span class="line">  <span class="keyword">private</span> Cache cache;</span><br><span class="line">  <span class="keyword">private</span> ParameterMap parameterMap;</span><br><span class="line">  <span class="keyword">private</span> List&lt;ResultMap&gt; resultMaps;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">boolean</span> flushCacheRequired;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">boolean</span> useCache;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">boolean</span> resultOrdered;</span><br><span class="line">  <span class="keyword">private</span> SqlCommandType sqlCommandType;</span><br><span class="line">  <span class="keyword">private</span> KeyGenerator keyGenerator;</span><br><span class="line">  <span class="keyword">private</span> String[] keyProperties;</span><br><span class="line">  <span class="keyword">private</span> String[] keyColumns;</span><br><span class="line">  <span class="keyword">private</span> <span class="type">boolean</span> hasNestedResultMaps;</span><br><span class="line">  <span class="keyword">private</span> String databaseId;</span><br><span class="line">  <span class="keyword">private</span> Log statementLog;</span><br><span class="line">  <span class="keyword">private</span> LanguageDriver lang;</span><br><span class="line">  <span class="keyword">private</span> String[] resultSets;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而其中有一个特别的属性就是我们的语句源 SqlSource ，功能纯粹也恰如其名 <strong>SQL 源</strong>。它是一个接口，它会结合用户传递的参数对象 parameterObject 与动态 SQL，生成 SQL 语句，并最终封装成 BoundSql 对象。SqlSource 接口有5个实现类，分别是：StaticSqlSource、DynamicSqlSource、RawSqlSource、ProviderSqlSource、<del>VelocitySqlSource</del> （而 velocitySqlSource 目前只是一个测试用例，还没有用作实际的 Sql 源实现）。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project06/sqlSource_all.jpg"></p><ul><li><strong>StaticSqlSource</strong>：静态 SQL 源实现类，所有的 SQL 源最终都会构建成 StaticSqlSource 实例，该实现类会生成最终可执行的 SQL 语句供 statement 或 prepareStatement 使用。</li><li><strong>RawSqlSource</strong>：原生 SQL 源实现类，解析构建含有 ‘#{}’ 占位符的 SQL 语句或原生 SQL 语句，解析完最终会构建 StaticSqlSource 实例。</li><li><strong>DynamicSqlSource</strong>：动态 SQL 源实现类，解析构建含有 ‘${}’ 替换符的 SQL 语句或含有动态 SQL 的语句（例如 If/Where/Foreach等），解析完最终会构建 StaticSqlSource 实例。</li><li><strong>ProviderSqlSource</strong>：注解方式的 SQL 源实现类，会根据 SQL 语句的内容分发给 RawSqlSource 或 DynamicSqlSource ，当然最终也会构建 StaticSqlSource 实例。</li><li><strong>VelocitySqlSource</strong>：模板 SQL 源实现类，目前（V3.5.6）官方申明这只是一个测试用例，还没有用作真正的模板 Sql 源实现类。</li></ul><p>SqlSource 实例在配置类 Configuration 解析阶段就被创建，Mybatis 框架会依据3个维度的信息来选择构建哪种数据源实例：（纯属我个人理解的归类梳理~）</p><ul><li><strong>第一个维度</strong>：客户端的 SQL 配置方式：XML 方式或者注解方式。</li><li><strong>第二个维度</strong>：SQL 语句中是否使用动态 SQL （ if/where/foreach 等 ）。</li><li><strong>第三个维度</strong>：SQL 语句中是否含有替换符 ‘${}’ 或占位符 ‘#{}’ 。</li></ul><p>SqlSource 接口只有一个方法 getBoundSql ，就是创建 BoundSql 对象。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">SqlSource</span> &#123;</span><br><span class="line"></span><br><span class="line">  BoundSql <span class="title function_">getBoundSql</span><span class="params">(Object parameterObject)</span>;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>通过 SQL 源就能够获取 BoundSql 对象，从而获取最终送往数据库（通过JDBC）中执行的 SQL 字符串。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/sql_life.jpg"></p><p> JDBC 中执行的 SQL 字符串，确实就在 BoundSql 对象中。BoundSql 对象存储了动态（或静态）生成的 SQL 语句以及相应的参数信息，它是在执行器具体执行 CURD 时通过实际的 SqlSource 实例所构建的。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">BoundSql</span> &#123; </span><br><span class="line"></span><br><span class="line">  <span class="comment">//该字段中记录了SQL语句，该SQL语句中可能含有&quot;?&quot;占位符</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> String sql;</span><br><span class="line">    </span><br><span class="line">  <span class="comment">//SQL中的参数属性集合</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> List&lt;ParameterMapping&gt; parameterMappings;</span><br><span class="line">    </span><br><span class="line">  <span class="comment">//客户端执行SQL时传入的实际参数值</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Object parameterObject;</span><br><span class="line">    </span><br><span class="line">  <span class="comment">//复制 DynamicContext.bindings 集合中的内容</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Object&gt; additionalParameters;</span><br><span class="line">    </span><br><span class="line">  <span class="comment">//通过 additionalParameters 构建元参数对象</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">final</span> MetaObject metaParameters;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>在执行器 Executor 实例（例如BaseExecutor）执行增删改查时，会通过 SqlSource 构建 BoundSql 实例，然后再通过 BoundSql 实例获取最终输送至数据库执行的 SQL 语句，系统可根据 SQL 语句构建 Statement 或者 PrepareStatement ，从而送往数据库执行，例如语句处理器 StatementHandler 的执行过程。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/StatementHandler.jpg"></p><blockquote><p>墙裂推荐阅读之前第六文之 Mybatis 最硬核的 API 你知道几个？这些执行流程都有细讲。</p></blockquote><p>到此我们介绍完 SQL 源 SqlSource 与 BoundSql 的关系，注意 SqlSource 与 BoundSql 不是同个阶段产生的，而是分别在程序启动阶段与运行时。 </p><ul><li><strong>程序启动初始构建时</strong>，框架会根据 SQL 语句类型构建对应的 SqlSource 源实例（静态/动态）.</li><li><strong>程序实际运行时</strong>，框架会根据传入参数动态的构建 BoundSql 对象，输送最终 SQL 到数据库执行。</li></ul><p>在上面我们知道了 SQL 源是语句对象 BoundSql 的属性，同时还坐拥5大实现类，那究竟是谁创建了 SQL 源呢？其实就是我们接下来准备介绍的语言驱动 LanguageDriver ！</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">interface</span> <span class="title class_">LanguageDriver</span> &#123;</span><br><span class="line">    SqlSource <span class="title function_">createSqlSource</span><span class="params">(...)</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p> 语言驱动接口 LanguageDriver 也是极简洁，内部定义了构建 SQL 源的方法，LanguageDriver 接口有2个实现类，分别是： XMLLanguageDriver 、 RawLanguageDriver。简单介绍一下：</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/languageDriver.jpg"></p><ul><li><strong>XMLLanguageDriver</strong> ：是框架默认的语言驱动，能够根据上面我们讲解的 SQL 源的3个维度创建对应匹配的 SQL 源（DynamicSqlSource、RawSqlSource等）。下面这段代码是 Mybatis 在装配全局配置时的一些跟语言驱动相关的动作，我摘抄出来，分别有：内置了两种语言驱动并设置了别名方便引用、注册了两种语言驱动至语言注册工厂、把 XML 语言驱动设置为默认语言驱动。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 全局配置的构造方法</span></span><br><span class="line"><span class="keyword">public</span> <span class="title function_">Configuration</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// 内置/注册了很多有意思的【别名】</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 其中就内置了上述的两种语言驱动【别名】</span></span><br><span class="line">    typeAliasRegistry.registerAlias(<span class="string">&quot;XML&quot;</span>, XMLLanguageDriver.class);</span><br><span class="line">    typeAliasRegistry.registerAlias(<span class="string">&quot;RAW&quot;</span>, RawLanguageDriver.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 注册了XML【语言驱动】 --&gt; 并设置成默认！   </span></span><br><span class="line">    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 注册了原生【语言驱动】</span></span><br><span class="line">    languageRegistry.register(RawLanguageDriver.class);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ul><li><strong>RawLanguageDriver</strong> ：看名字得知是原生语言驱动，事实也如此，它只能创建原生 SQL 源（RawSqlSource），另外它还继承了 XMLLanguageDriver 。</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * As of 3.2.4 the default XML language is able to identify static statements</span></span><br><span class="line"><span class="comment"> * and create a &#123;<span class="doctag">@link</span> RawSqlSource&#125;. So there is no need to use RAW unless you</span></span><br><span class="line"><span class="comment"> * want to make sure that there is not any dynamic tag for any reason.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@since</span> 3.2.0</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span> Eduardo Macarron</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RawLanguageDriver</span> <span class="keyword">extends</span> <span class="title class_">XMLLanguageDriver</span> &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注释的大致意思：自 Mybatis 3.2.4 之后的版本， XML 语言驱动就支持解析静态语句（动态语句当然也支持）并创建对应的 SQL 源（例如静态语句是原生 SQL 源），所以除非你十分确定你的 SQL 语句中没有包含任何一款动态标签，否则就不要使用 RawLanguageDriver ！否则会报错！！！先看个别名引用的例子：</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>  <span class="attr">resultType</span>=<span class="string">&quot;map&quot;</span> <span class="attr">lang</span>=<span class="string">&quot;RAW&quot;</span> &gt;</span></span><br><span class="line">     select * from user</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- 别名或全限定类名都允许 --&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="name">select</span> <span class="attr">id</span>=<span class="string">&quot;findAll&quot;</span>  <span class="attr">resultType</span>=<span class="string">&quot;map&quot;</span> <span class="attr">lang</span>=<span class="string">&quot;org.apache.ibatis.scripting.xmltags.XMLLanguageDriver&quot;</span>&gt;</span></span><br><span class="line">     select * from user</span><br><span class="line"><span class="tag">&lt;/<span class="name">select</span>&gt;</span></span><br></pre></td></tr></table></figure><p>框架允许我们通过 lang 属性手工指定语言驱动，不指定则系统默认是 lang = “XML”，XML 代表 XMLLanguageDriver ，当然 lang 属性可以是我们内置的别名也可以是我们的语言驱动全限定名，不过值得注意的是，当语句中含有动态 SQL 标签时，就只能选择使用 lang=”XML”，否则程序在初始化构件时就会报错。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">## Cause: org.apache.ibatis.builder.BuilderException: </span><br><span class="line">## Dynamic content is not allowed when using RAW language</span><br><span class="line">## 动态语句内容不被原生语言驱动支持！</span><br></pre></td></tr></table></figure><p>这段错误提示其实是发生在 RawLanguageDriver 检查动态 SQL 源时：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RawLanguageDriver</span> <span class="keyword">extends</span> <span class="title class_">XMLLanguageDriver</span> &#123; </span><br><span class="line"></span><br><span class="line">  <span class="comment">// RAW 不能包含动态内容</span></span><br><span class="line">  <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">checkIsNotDynamic</span><span class="params">(SqlSource source)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!RawSqlSource.class.equals(source.getClass())) &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">BuilderException</span>(</span><br><span class="line">          <span class="string">&quot;Dynamic content is not allowed when using RAW language&quot;</span></span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>至此，基本逻辑我们已经梳理清楚：程序启动初始阶段，语言驱动创建 SQL 源，而运行时， SQL 源动态解析构建出 BoundSql 。</p><blockquote><p>那么除了系统默认的两种语言驱动，还有其它吗？</p></blockquote><p>答案是：有，例如 Mybatis 框架中目前使用了一个名为 VelocityLanguageDriver 的语言驱动。相信大家都学习过 JSP 模板引擎，同时还有很多人学习过其它一些（页面）模板引擎，例如 freemark 和 velocity ，不同模板引擎有自己的一套模板语言语法，而其中 Mybatis 就尝试使用了 Velocity 模板引擎作为语言驱动，目前虽然 Mybatis 只是在测试用例中使用到，但是它告诉了我们，框架允许自定义语言驱动，所以不只是 XML、RAW 两种语言驱动中使用的 OGNL 语法，也可以是 Velocity （语法），或者你自己所能定义的一套模板语言（同时你得定义一套语法）。 例如以下就是 Mybatis 框架中使用到的 Velocity 语言驱动和对应的 SQL 源，它们使用 Velocity 语法/方式解析构建 BoundSql 对象。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Just a test case. Not a real Velocity implementation.</span></span><br><span class="line"><span class="comment"> * 只是一个测试示例，还不是一个真正的 Velocity 方式实现</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VelocityLanguageDriver</span> <span class="keyword">implements</span> <span class="title class_">LanguageDriver</span> &#123;</span><br><span class="line">     <span class="keyword">public</span> SqlSource <span class="title function_">createSqlSource</span><span class="params">()</span> &#123;...&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">VelocitySqlSource</span> <span class="keyword">implements</span> <span class="title class_">SqlSource</span> &#123;</span><br><span class="line">     <span class="keyword">public</span> BoundSql <span class="title function_">getBoundSql</span><span class="params">()</span> &#123;...&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>好，语言驱动的基本概念大致如此。我们回过头再详细看看动态 SQL 源 SqlSource，作为语句对象 MappedStatement 的属性，在 <strong>程序初始构建阶段</strong>，语言驱动是怎么创建它的呢？不妨我们先看看常用的动态 SQL 源对象是怎么被创建的吧！</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/init_sqlsource.jpg"></p><p>通过以上的程序初始构建阶段，我们可以发现，最终语言驱动通过调用 XMLScriptBuilder 对象来创建 SQL 源。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// XML 语言驱动</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">XMLLanguageDriver</span> <span class="keyword">implements</span> <span class="title class_">LanguageDriver</span> &#123;  </span><br><span class="line">  </span><br><span class="line">    <span class="comment">// 通过调用 XMLScriptBuilder 对象来创建 SQL 源</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">public</span> SqlSource <span class="title function_">createSqlSource</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">// 实例</span></span><br><span class="line">    <span class="type">XMLScriptBuilder</span> <span class="variable">builder</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">XMLScriptBuilder</span>();</span><br><span class="line">    <span class="comment">// 解析</span></span><br><span class="line">        <span class="keyword">return</span> builder.parseScriptNode();</span><br><span class="line">&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>而在前面我们就已经介绍， XMLScriptBuilder 实例初始构造时，会初始构建所有动态标签处理器：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// XML脚本标签构建器</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">XMLScriptBuilder</span>&#123;</span><br><span class="line">    <span class="comment">// 标签节点处理器池</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, NodeHandler&gt; nodeHandlerMap = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;&gt;();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 构造器</span></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">XMLScriptBuilder</span><span class="params">()</span> &#123; </span><br><span class="line">        initNodeHandlerMap();</span><br><span class="line">        <span class="comment">//... 其它初始化不赘述也不重要</span></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 动态标签处理器</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">void</span> <span class="title function_">initNodeHandlerMap</span><span class="params">()</span> &#123;</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;trim&quot;</span>, <span class="keyword">new</span> <span class="title class_">TrimHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;where&quot;</span>, <span class="keyword">new</span> <span class="title class_">WhereHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;set&quot;</span>, <span class="keyword">new</span> <span class="title class_">SetHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;foreach&quot;</span>, <span class="keyword">new</span> <span class="title class_">ForEachHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;if&quot;</span>, <span class="keyword">new</span> <span class="title class_">IfHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;choose&quot;</span>, <span class="keyword">new</span> <span class="title class_">ChooseHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;when&quot;</span>, <span class="keyword">new</span> <span class="title class_">IfHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;otherwise&quot;</span>, <span class="keyword">new</span> <span class="title class_">OtherwiseHandler</span>());</span><br><span class="line">        nodeHandlerMap.put(<span class="string">&quot;bind&quot;</span>, <span class="keyword">new</span> <span class="title class_">BindHandler</span>());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>继 XMLScriptBuilder  初始化流程之后，解析创建 SQL 源流程再分为两步：</p><p><strong>1、解析动态标签</strong>，通过判断每一块动态标签的类型，使用对应的标签处理器进行解析属性和语句处理，并最终放置到混合 SQL 节点池中（MixedSqlNode），以供程序运行时构建 BoundSql 时使用。</p><p><strong>2、new SQL 源</strong>，根据 SQL 是否有动态标签或通配符占位符来确认产生对象的静态或动态 SQL 源。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> SqlSource <span class="title function_">parseScriptNode</span><span class="params">()</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 1、解析动态标签 ，并放到混合SQL节点池中</span></span><br><span class="line">    <span class="type">MixedSqlNode</span> <span class="variable">rootSqlNode</span> <span class="operator">=</span> parseDynamicTags(context);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2、根据语句类型，new 出来最终的 SQL 源</span></span><br><span class="line">    SqlSource sqlSource;</span><br><span class="line">    <span class="keyword">if</span> (isDynamic) &#123;</span><br><span class="line">      sqlSource = <span class="keyword">new</span> <span class="title class_">DynamicSqlSource</span>(configuration, rootSqlNode);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      sqlSource = <span class="keyword">new</span> <span class="title class_">RawSqlSource</span>(configuration, rootSqlNode, parameterType);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> sqlSource;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>原来解析动态标签的工作交给了 parseDynamicTags() 方法，并且每一个语句对象的动态 SQL 标签最终都会被放到一个混合 SQL 节点池中。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 混合 SQL 节点池</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MixedSqlNode</span> <span class="keyword">implements</span> <span class="title class_">SqlNode</span> &#123;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 所有动态 SQL 标签：IF、WHERE、SET 等</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> List&lt;SqlNode&gt; contents;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们先看一下 SqlNode 接口的实现类，基本涵盖了我们所有动态 SQL 标签处理器所需要使用到的节点实例。而其中混合 SQL 节点 MixedSqlNode 作用仅是为了方便获取每一个语句的所有动态标签节点，于是应势而生。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/Mybatis/project8/SqlNode2.jpg"></p><p>知道动态 SQL 标签节点处理器及以上的节点实现类之后，其实就能很容易理解，<strong>到达程序运行时</strong>，执行器会调用 SQL 源来协助构建 BoundSql 对象，而 SQL 源的核心工作，就是根据每一小段标签类型，匹配到对应的节点实现类以解析拼接每一小段 SQL 语句。</p><p><strong>程序运行时，动态 SQL 源获取 BoundSql 对象 ：</strong></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 动态 SQL 源</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">DynamicSqlSource</span> <span class="keyword">implements</span> <span class="title class_">SqlSource</span> &#123; </span><br><span class="line">   </span><br><span class="line">    <span class="comment">// 这里的 rootSqlNode 属性就是 MixedSqlNode </span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SqlNode rootSqlNode;</span><br><span class="line">  </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> BoundSql <span class="title function_">getBoundSql</span><span class="params">(Object parameterObject)</span> &#123;</span><br><span class="line"> </span><br><span class="line">        <span class="comment">// 动态SQL核心解析流程  </span></span><br><span class="line">        rootSqlNode.apply(...);  </span><br><span class="line">        </span><br><span class="line">        <span class="keyword">return</span> boundSql;</span><br><span class="line"></span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>很明显，通过调用 MixedSqlNode 的 apply () 方法，循环遍历每一个具体的标签节点。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MixedSqlNode</span> <span class="keyword">implements</span> <span class="title class_">SqlNode</span> &#123;</span><br><span class="line">    </span><br><span class="line">      <span class="comment">// 所有动态 SQL 标签：IF、WHERE、SET 等</span></span><br><span class="line">      <span class="keyword">private</span> <span class="keyword">final</span> List&lt;SqlNode&gt; contents; </span><br><span class="line"></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">apply</span><span class="params">(...)</span> &#123;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 循环遍历，把每一个节点的解析分派到具体的节点实现之上</span></span><br><span class="line">        <span class="comment">// 例如 &lt;if&gt; 节点的解析交给 IfSqlNode</span></span><br><span class="line">        <span class="comment">// 例如 纯文本节点的解析交给 StaticTextSqlNode</span></span><br><span class="line">        contents.forEach(node -&gt; node.apply(...));</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">      &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们选择一两个标签节点的解析过程进行说明，其它标签节点实现类的处理也基本雷同。首先我们看一下 IF 标签节点的处理：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// IF 标签节点</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">IfSqlNode</span> <span class="keyword">implements</span> <span class="title class_">SqlNode</span> &#123; </span><br><span class="line">    </span><br><span class="line">      <span class="keyword">private</span> <span class="keyword">final</span> ExpressionEvaluator evaluator;</span><br><span class="line">    </span><br><span class="line">      <span class="comment">// 实现逻辑</span></span><br><span class="line">      <span class="meta">@Override</span></span><br><span class="line">      <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">apply</span><span class="params">(DynamicContext context)</span> &#123;</span><br><span class="line">          </span><br><span class="line">        <span class="comment">// evaluator 是一个基于 OGNL 语法的解析校验类</span></span><br><span class="line">        <span class="keyword">if</span> (evaluator.evaluateBoolean(test, context.getBindings())) &#123;</span><br><span class="line">          contents.apply(context);</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">      &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>IF 标签节点的解析过程非常简单，通过解析校验类 ExpressionEvaluator 来对 IF 标签的 test 属性内的表达式进行解析校验，满足则拼接，不满足则跳过。我们再看看 Trim 标签的节点解析过程，set 标签与 where 标签的底层处理都基于此：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">TrimSqlNode</span> <span class="keyword">implements</span> <span class="title class_">SqlNode</span> &#123; </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 核心处理方法</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">applyAll</span><span class="params">()</span> &#123;</span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 前缀智能补充与去除</span></span><br><span class="line">        applyPrefix(..); </span><br><span class="line">        </span><br><span class="line">        <span class="comment">// 前缀智能补充与去除</span></span><br><span class="line">        applySuffix(..); </span><br><span class="line">    &#125; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>再来看一个纯文本标签节点实现类的解析处理流程：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 纯文本标签节点实现类</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">StaticTextSqlNode</span> <span class="keyword">implements</span> <span class="title class_">SqlNode</span> &#123;</span><br><span class="line">  </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String text;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="title function_">StaticTextSqlNode</span><span class="params">(String text)</span> &#123;</span><br><span class="line">        <span class="built_in">this</span>.text = text;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 节点处理，仅仅就是纯粹的语句拼接</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="type">boolean</span> <span class="title function_">apply</span><span class="params">(DynamicContext context)</span> &#123;</span><br><span class="line">        context.appendSql(text);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>到这里，动态 SQL 的底层解析过程我们基本讲解完，冗长了些，但流程上大致算完整，有遗漏的，我们回头再补充。</p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/05.png"></p><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>不知不觉中，我又是这么巨篇幅的讲解剖析，确实不太适合碎片化时间阅读，不过话说回来，毕竟此文属于 Mybatis 全解系列，作为学研者还是建议深谙其中，对往后众多框架技术的学习必有帮助。本文中我们很多动态 SQL 的介绍基本都使用 XML 配置方式，当然注解方式配置动态 SQL 也是支持的，动态 SQL 的语法书写同 XML 方式，但是需要在字符串前后添加 script 标签申明该语句为动态 SQL ，例如：</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">UserDao</span> &#123;</span><br><span class="line">   </span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 更新用户</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Select(</span></span><br><span class="line"><span class="meta">        &quot;&lt;script&gt;&quot;+</span></span><br><span class="line"><span class="meta">        &quot;   UPDATE user   &quot;+</span></span><br><span class="line"><span class="meta">        &quot;   &lt;trim prefix=\&quot;SET\&quot; prefixOverrides=\&quot;,\&quot;&gt; &quot;+</span></span><br><span class="line"><span class="meta">        &quot;       &lt;if test=\&quot;username != null and username != &#x27;&#x27;\&quot;&gt; &quot;+</span></span><br><span class="line"><span class="meta">        &quot;           , username = #&#123;username&#125; &quot;+</span></span><br><span class="line"><span class="meta">        &quot;       &lt;/if&gt; &quot;+</span></span><br><span class="line"><span class="meta">        &quot;   &lt;/trim&gt; &quot;+</span></span><br><span class="line"><span class="meta">        &quot;   where id = $&#123;id&#125;&quot;</span></span><br><span class="line"><span class="meta">        &quot;&lt;/script&gt;&quot;</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">updateUser</span><span class="params">( User user)</span>;</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>此种动态 SQL 写法可读性较差，并且维护起来也挺硌手，所以我个人是青睐 xml 方式配置语句，一直追求解耦，大道也至简。当然，也有很多团队和项目都在使用注解方式开发，这些没有绝对，还是得结合自己的实际项目情况与团队等去做取舍。</p><blockquote><p>本篇完，本系列下一篇我们讲《 <strong>Mybatis系列全解（九）：Mybatis的复杂映射</strong> 》。</p></blockquote><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/emoji/next.png"></p><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/sourceMaterial/article_the_end.png"></p><blockquote><p>文章持续更新，微信搜索「<strong>第一次当爸爸吖</strong>」第一时间阅读，随时有惊喜。本文会在 <strong>GitHub</strong> <a href="https://github.com/senlypan/JavaWorld">https://github.com/JavaWorld</a> 收录，关于热腾腾的技术、框架、面经、解决方案、摸鱼技巧、教程、视频、漫画等等等等，我们都会以最美的姿势第一时间送达，欢迎 Star ~ 我们未来 <strong>不止文章</strong>！想进读者群的朋友欢迎撩我个人号：panshenlian，备注「<strong>加群</strong>」我们群里畅聊， <strong>BIU ~</strong> </p></blockquote><p><img src="https://www.panshenlian.com/images/post/00_old_article_images/emoji/love.png"></p>]]></content:encoded><description>&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/post/java/mybatis/title/08-title.jpg"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.panshenlian.com/images/p</description></item></channel></rss>