往者不可谏,来者犹可追

2024年的时间是被繁重的工作填满的一年,很多年初计划好的事情,因为忙碌的工作搞得身心俱疲,没有精力去做。这两天在回顾过去这一年有什么成长,收获了什么,失去了什么,最大的感触就是工作已经占据了我绝大部分的时间和注意力,导致现在往回看的时候,对这一年是过得不太满意的,因为在工作的驱使压迫下,失去了自己的节奏,没有把时间合理的分配到自己想做的事情上。因为收获不多,对于盘点这一年的得与失是有点回避心理的,但是每年例行的习惯,还是想做一下这件事,仔细回看一下,这一年时间都去哪里了,踏浪莫忘回首嘛。

工作仍然是主旋律,从毕业开始就一直挺不喜欢做移动端开发的,多数的时间都在调整UI和处理各种恶心的机型适配,常常感觉没有技术含量和前途。在今年三月,通过公司内部转岗,开始做后端开发。业务逻辑确实比移动端更加复杂,工作量变多,加上自己不熟悉工作,导致一整年都在加班。在工作上花了很多时间,也踩了很多坑,经验教训都不少,需要好好想想,吃一堑长一智。

今年考到了摩托车证。从去年开始报名,在今年七月拿到了证件,距离摩旅G219又近了一步。长期的在无聊的工作中消耗生命,就越发的想要去无人的荒野上驰骋。对于摩旅这件事既想冲动的出发,让生命变得更有意义,又不断在要不要裸辞的想法中挣扎,身处在现实社会中,就要考虑就业和生活问题。处在一种很隔离的状态,心里想鲜衣怒马流浪天涯,实际是做牛做马一天不落。最后要不要决定出发,就要看自己更想要的是什么了,总不能做什么都是既要要又要的心态。

存钱,目前看达到了年初的既定目标,实现了100+个的目标,这个没有什么悬念,就是靠工资,然后不乱花就行。对于花钱的观点,秉持着合理控制自己消费欲望,对家人尽量大方的原则,做到不乱花钱,但是也不用刻意节约,这种方式刚好够满足自己的要求。感觉目前工作没有什么热情,没有主动投入思考的欲望,也就不算喜欢,这不是长久之计,没办法一直做自己不喜欢的工作。未来可能希望做一些轻松一点、有主动投入精力的欲望的工作,能有更多的时间做自己喜欢做的事情,这也需要提前存一些钱准备一下物质基础。

2024年也出门旅游了几次,总体的感觉就是,不同的城市会有一点自己的特色,但是好像所有的城市都在趋同。一月去了上海和苏州,上海的特色就是现代,苏州还保有很多白墙灰瓦的建筑,但是商业化也很严重。三月去了重庆,对重庆的印象是很辣很辣的火锅,四月去了武汉,对武汉的肥肥虾印象很好,下次还想吃。九月去了吉林,看了长白山,很惊艳,觉得不虚此行。

今年开始把有计划的进行技术博客的写作,一方面是为了补课,另一方面是为了平时有目的性的积累,方便面试的时候体现出个人对专业的热爱和投入,建立人设。整体上来说,也没用达成年初设定的目标,预期是十篇,从数量上来说只有七篇,另一方面是在写作质量上,都是学习性的笔记类型,缺乏从工作实践中积累的主题,有点泛泛而谈的感觉。

今年也有继续在抽时间阅读和专业无关的书籍,《基金投资》、《有钱人和你想的不一样》、《置身事内》、《黑天鹅》、《时间简史》、《人类简史》和《未来简史》。每次读完一本书其实能够记下来的并不多,有时候甚至花几周读完的一本书,也大概只能记下来几个章节的主旨大意或者一两段话,其实这样也就够了,这一两句话至少唤起了我们的一些共鸣,让我们更清晰的认同了我们已有的一些想法,或者告诉了我们一些简单的道理。比如,读《黑天鹅》时,对下面这句话狠狠的赞同了:

想象一个10亿倍于地球的行星边缘上的一粒尘埃。这粒尘埃就代表你出生的概率,庞大的行星则代表相反的概率。所以不要再为小事烦恼了。不要再像一个忘恩负义者,得到一座城堡,还要介意浴室里的霉菌。不要再检查别人赠予你的马匹的牙齿,请记住,你就是黑天鹅。

每年都希望有计划的投入足够多的时间去锻炼身体,也一直坚持在做这件事,但是今年这件事是做得最差的。年初还能坚持一段时间,打羽毛球、跑步或者健身,后来渐渐的空闲时间都被工作挤满,有点空闲也只想躺着了。锻炼身体的益处有很多,一方面是长期的健康,另一方面是压力的宣泄,每次跑完半小时,感觉整个身体都轻盈了许多。来年希望在坚持锻炼这件事上可以做得更好。时间的惯性是巨大的,长期坚持做一件事其实并不会感觉很累,同样的,躺在床上休息两天也不会感觉有多放松,锻炼身体不会是一朝一夕的事情,需要把量均匀的分散在每一天中,慢慢坚持,慢慢从中获益。

2025又是新的一年,老规矩,想了一些要做的事情:

  • 坚持务实主义,换一份工作,实现一定程度的涨薪;
  • 坚持专业学习,专注在常用组件和框架、工作方法、架构设计等方向,输出技术博客十篇;
  • 坚持锻炼身体,一小时锻炼量超过100次,体重减到130斤以下;
  • 坚持阅读,主题在传记、通用能力等方向,阅读量超过10本;
  • 学习吉他,能够熟练演奏五首歌曲;
  • 买一辆自己的摩托,并能够熟练驾驶,为摩旅做准备;
  • 在副业方向做一些探索,实现除了工资之外有其他收入来源;
  • 考系统架构设计师(高级)、PMP项目管理师、系统分析师;

2025年,保持全力以赴的态度。


今日简史

科技在进步,人类就能生活得更好吗?客观来说,科学技术是提高生产力的有效手段,举个例子,在古代,通过牛耕地,现在机械化和大棚种植,生产效率肯定是提高了的;相比通过书信传递信息的时代,现在可以随时随地联系好友;现在的飞机、高铁也比古代的马车快了很多倍。但是,人类有因为科学技术进步而变得更轻松,更幸福吗?这个问题没有办法直接说是变得幸福了或者变得痛苦了,每个人都有不同的看法,甚至一个人不同时期也有不同的看法。人口预期寿命、婴儿死亡率等不可否认的随着医疗条件提高得到了改善,改良作物和机械化种植使得粮食产量、种类大幅提升,人们却是吃得更好了,这些都是科技进步的好处。但是,科技发展也带来了很多问题,环境污染,气候变暖等,动物灭绝速度加快等,都在影响人类的生存。核威胁,就像悬在全人类头上的一把剑,如果持有核武器的国家失控,引发战争,顷刻之间,成百上千的人就会失去生命。对个人而言,好像由于科技的进步人们变得越来约忙碌,越来越孤单了,通讯设备让人们随时随地在工作,便捷的交通工具加速了人们的迁徙,与社群的联系越来越弱。人们在不断发展科技的同时,需要好好想一下,怎么运用科技来造福人类也很重要。

大数据时对我们的生活都有哪些影响?当代社会,人们已经离不开互联网了。使用网络购物、社交、解决衣食住行的各种问题。这就导致我们大量的私人信息保留在了这些服务提供者的服务器里面,通过这些数据,分析我们的习惯、爱好等,再给我们推荐各种他们希望我们看到的东西。长此以往,第一个问题就是信息茧房,算法通过分析我们的习惯,越来约精准的推荐我们感兴趣的东西,看到的也只是我们感兴趣的,对这个世界的了解的渠道逐渐变得单一,这是很危险的,我们每个人的对世界的了解变得片面,产生偏见等,和其他人的交往、共情都将变得困难,主动获取信息,过滤掉不必要的信息将会非常重要。另一个问题是隐私问题,我们那么多私人数据,由服务提供商保存,他们能确保数据安全并且不作他用吗?没有人能够保证,时常收到各种骚扰信息是小问题,个人隐私信息泄漏导致甚至可能导致诈骗和人身安全问题。

社会快速发展,教育怎么跟上步伐?在古代,中国学习四书五经,射箭弹琴,到了现代,学习内容虽然有了一些变化,但是教育模式等还是没有太大区别。但是这个社会的发展是越来越迅速的,我们会继续保持这种教育模式下去吗?未来机器人和人工智能的运用越来越普及,这是趋势,所以很多简单重复的工作都将被取代。又一个论调是,每次科技革命淘汰掉一些岗位,都会催生新的岗位。这个观点我认同,但是被淘汰掉的工人呢?可以进入到其他岗位吗?这很难说。因为未来的工作岗位一定是工作要求比较高的,不是有手就行,不能通过简单的培训就可以上岗的。办法也也许不止一个,但是重视未来的变化,拥抱变化,坚持学习对多数人才是根本办法。没有办法想象未来三十年是什么样子,需要哪些人才,哪些岗位又会被淘汰掉,但是人才要求一定是越来越高的,所以教育模式也得跟上科技发展。

战争和恐怖主义的一点理解。世界上的大国,每年都会投入巨额资金用于发展军事武器,很多高科技产品最开始都是由军事目的研发的,淘汰以后才是民用,所以现在最高端的武器都是在各个国家的军队里面。原子弹,氢弹已经诞生80多年了,这80年间不知道又研发了多少威力更大的武器,如果拥有核武器的国家在战争中使用核武器,顷刻之间,就会有无数人丧命。现在只能寄希望于各个国家是非常冷静的,不会随意使用,但是这种把人类安全交给人的理智来决定,还是多少让人感到不安。恐怖主义,每年因此丧命的人少之又少,但是为什么大家谈之色变呢?《今日简史》作者的观点是之前没有看到过的,发达恐怖主义袭击的人都是弱者,他们无法通过发动战争来获得他们希望的好处,所以通过发动小规模袭击,大肆宣传,让人们恐慌,只要政府恐慌,应付措施就会发生变形,恐怖主义者就有了可乘之机。政府应对恐怖主义袭击本来可以通过静悄悄的方式来打击,但是为了让民众知道自己在行动,就要大张旗鼓的行动,但大张旗鼓往往容易旁生枝节,正中恐怖主义者的下怀。


黑天鹅

《黑天鹅》这本书是2024年读完的第一本书,从2023年年底,看到2024年,可以说花了两年时间。今年年初立下Flag,每读完一本书,都写一点东西,保持输入的同时,也要保持输出。

在发现澳大利亚黑天鹅之前,所有的欧洲人都认为天鹅的羽毛都是白色的,当第一次发现黑天鹅时,人们关于天鹅羽毛颜色是白色的牢不可破的信念即被打破了,所以黑天鹅表示那些发生概率极低,但是影响巨大且深远的事件。

首先想抱怨一下,这本书真的让人感觉很冗长,一些表达很发散,但是在章节结尾没有回归到想要表达的思想上来,尤其是一些举例,给人无疾而终、戛然而止的感觉。部分章节数学计算也非必要,推导过程看得让人发昏。瑕不掩瑜,整体来看这本书还是很值得一读的,首先是核心思想就打破常规,提醒读者不要因为一些稀少事件发生概率低就选择无视,往往是这些事件的让历史或者个人人生发展轨迹发生大转弯,其次作者通过一些举例,反驳了一些概率理论,或者来自权威专家或者来自课本,是如何根深蒂固的影响着我们,但是被错误使用的。

选择了一些书中的观点,结合个人思考,说一下个人的理解。

历史从不爬行,只是跃迁。作者认为历史并不是平稳的发展的,把时间维度拉长来看,总是一些偶然事件,极大的推动了历史的发展。确实,这个观点我觉得是正确的。比如原始人因为偶然发现了一些种植谷类,逐渐从采集社会发展到种植定居社会、发现新大论和麦哲伦环球这样偶然的事件导致了黑奴贸易,殖民和现在的种族问题、一些偶然的科学发现极大的推动了生产力的提高等等。对于个人,关注这些需要很长时间才能显现影响力的宏观事件意义并不大,但是个人的人生轨迹应该也不是平稳向前的,也充斥着各种或好或坏的随机事件,我们应该以什么样的态度去看待人生的稳定和动荡呢?我认为,人生轨迹不是线性的,或者说没有什么发展规律是线性的,前进必然伴随着起起伏伏。拿职业发展来说,今天平平无奇的去上班然后顺利下班,不代表以后每一天都会这样,说不定在未来某一天会遇到职业的危机或者机遇,我们能做的,是掌控自己能掌控的部分,多主动一点,提升自己的能力、协调自己的发力方向和行业发展方向,做到同向而行。追求稳定断不可取,变化才是唯一不变的,如作者所说,不要做一只火鸡,前999天喂食的手,也可能在第1000天拧断它的脖颈。

对预测始终保持怀疑,尤其是宏观数据的预测。每年年底或者年初,都有大量专家或者权威机构发布一些预测数据,比如明年经济如何增长,20年后人口数据将达到什么水平等等。这个预测过程就如同一个黑盒,也许大街上随便找个人猜测也能比他们预测的准,而且他们总是排出稀有事件的影响,但是稀有事件常常具有影响整体的能量。感觉这一部分挺枯燥的,点到为止。

职业间有一个区分,是否赢者通吃。比如理发师、厨师、医生等,无法通过深耕自己的手艺,做到行业头部水平,然后获取绝大多数的利润。但是歌手、演员、作家等,不鸣则已,一鸣惊人,1%的从业者有可能分90%的蛋糕,销量100万本的书,大概率是一个作者卖出了990万本,一个作者卖出了1w本而不是两个人卖的差不多。在选择职业的时候,也要根据自己的优势和劣势,取迎合或者规避这样的行业,自己能不能坐冷板凳,有没有耐心长期深耕本专业、能不能承受最终还是不能脱颖而出的后果。

最喜欢作者本书结尾的一段话,特别治愈,原文抄录:

想象一个10亿倍于地球的行星边缘上的一粒尘埃。这粒尘埃就代表你出生的概率,庞大的行星则代表相反的概率。所以不要再为小事烦恼了。不要再像一个忘恩负义者,得到一座城堡,还要介意浴室里的霉菌。不要再检查别人赠予你的马匹的牙齿,请记住,你就是黑天鹅。


人类简史

2024年初定下了一个目标,每阅读完一本书就要写一点文字记录一下阅读感受,一方面是为了刻意加深印象,另一方面是为了锻炼写作能力。阅读完《人类简史》大概已经一个月了,一直偷懒没有写,清明节有空,想写下来。

从动物成为神,人依靠的是什么?对人类发展历史最朴实的理解是,在人类发展早期,人和其他动物在生活习惯、力量对比等并没有太悬殊的区别。人类住在山洞里、过着茹毛饮血的生活。但是从现在来看,人类和其他动物依然不能混为一谈,人类从某种视角来说,已经成为了神。是依靠什么,让人类与其他动物走出截然不同的路子的呢?第一个原因,应该是发展出了农业。人类一开始过着采集生活,可能只是某个偶然的发现,早期人类发现可以通过种植获得食物,由此逐渐开始过上定居的生活,自然而然的在周围建设生活设施,将富余的猎物养下来,驯化了动物,过上了越来约富足的生活。另一个更重要的原因是人类可以进行超大规模的协作。一个人斗不过一只狮子,五个人估计也斗不过五只狮子,但是100个人通过沟通、协作,就可以将100只狮子关入笼中,这依靠的就是人类共同的社会协作能力。在现代,通过协作,人类已经实现了很多其他物种不可能实现的事情,有秩序的全球贸易网络、人口众多的国家的有序运行、超复杂工程的顺利实现等,依靠的都是人类制定出的规则和共同遵守这套协作规则的能力。

人类社会不断发展,人们变得幸福了吗?人类进入农业社会以后,由于粮食生产量的增加,足以养活更多人口,所以人口也爆发式的增长,增长的人口又可以继续投入到开垦田地和农业生产中,如此循环,人口发生了爆炸式的增长。从生物学的角度来说,一个物种的基因拷贝数越多,这个物种就是越成功的,所以人类相比于其他动物来说,物种发展是成功的。但是个人有在随着物种的壮大而变得幸福吗?早期人类过着采集社火,采摘野果,捕猎其他动物,食物富余时也可能几天不用去狩猎,食物缺乏时,一起饿肚子。但是发展进入农业社会以后,食物开始变得更多,就开始出现了精英阶级和剥削。官员、地主等不产与实际生产,但是却分配到了更多的资源。现代社会更是如此,极少数的人掌握着绝大多数的资源,而负责生产的人却越来越忙,生活水平得不到提高。可见,物种壮大对个体而言不一定是好事,因为发展出了分工、法律、高级生产工具等,这些实际上可以成为奴役普通人的绳索。现在国家开始鼓励生育,我理解是因为出现劳动力不足的问题了,但是站在劳动者层面,是应该抵制生育的,劳动力稀缺才能增加劳动者和资本家的议价筹码。出生人口越多,劳动力越廉价,你我就越廉价。

消费主义盛行,身处其中该何去何从?如果是20世纪中叶是社会主义和资本主义两大阵营,那现在基本上是资本主义一家独大了。资本主义希望资本家将利润投入到再生产中,扩大生产。单只有生产没有消费时不行的,所以还拼命的鼓动社会成员消费各种产品,不管你是否需要,无所不用其极的让大家去购买。由此带来了浪费资源、攀比、物质主义等问题。所以我反对消费主义,因为过度使用不需要的产品,实际上是在支持不必要的扩大生产,由此可能带来环境污染,能源浪费等问题;其次是,过度购买就需要足够的钱,自己就只能不断工作,忙忙碌碌上班却依旧不能产生自己的原始积累,导致自己只能不断工作去支撑自己的购买欲望;还有一个对个人的影响,满目玲琅的商品会分散自己的注意力,这通常会影响自己的内心宁静。


2023年度总结

今天是2024年的一月一日了,终于开始写2024年的年度总结了。本来想昨天写的,但是昨天早上起来就开始打扫卫生,然后去打羽毛球,下午约了济源他们去吃比格自助,晚上一起看了B站的跨年晚会,就把年度总结留到了今天了。其实之前一直犹豫要不要写,因为感觉好像今年很多想做的事都没做,但是又想写一下,回顾一下今年的得失和成长。

做了哪些事?

雅思6分

考雅思这件事的想法由来已久,2021年,大四下学期的时候,课程比较少,闲着没事做就买了资料来复习,后来因为去骑行川藏线搁置了。2022年5月面试微软挂掉了,深感外语的重要性,决心提高英语。

复习过程战线拉得太长了,所以感觉进步不大。阅读准确率在28/40 到34/40之间左右徘徊。听力是略有进步的,但是不稳定,正确率从刚开始的18/40 到32/40之间,两次考试都没发挥好,很遗憾。写作还行,通过看范文,记忆句子等方式,6分还是可以的。口语分数很低,后来想着应该是方法不对,长期通过阅读对话备考的方式效果不好,即便在Cambly上报名模拟练习,也没啥效果,根本原因还是因为口语是长期积累的结果,考试不但是表达,也需要能听清楚,明确理解考官的问题。

复习到了2023年5月第一次参加考试,总分6分,感觉没发挥好,8月再考了一次,还是6分,报名费也不便宜,就先这样了。就当是打基础了,以后需要成绩证明的时候再去突击吧。

专业能力进步

要细数在工作技能上有哪些进步,其实不太好衡量,但是升职加薪应该是可以作为一个辅助衡量方式的。春季张新7%,秋季晋升成功张新15%,目前还是算比较满意的。工作技能上的提升还是有一些的:

编码能力,以Java和Kotlin语言为主,基本是可以熟练使用了,但是以后还需要重点关注Kotlin语言的一些特性,编程时多使用Kotlin,尽量写出地道的Kotlin代码。

方案设计能力,这非常重要,小到一个需求,大到一个项目,方案设计完善,组织评审以后再去落地开发,这样可以避免返工。而且方案设计文档也是晋升评审中的重要材料。

性能优化能力,这块确实比较弱,基本没有涉足,以后要刻意培养,这是迈向高阶的必经之路。

沟通技巧,这个不好说提升了什么,核心关键点就是说话要更加慎重,考略周全以后再说,切记毛毛躁躁,说话考虑不周,这回给自己挖坑,也给别人造成不靠谱的印象。

一些从工作中积累到的认知:

  • 功在平时,这是2022年晋升时leader说过的话,现在深以为然。我们所追求的,比较重要的目标,通常不是可以一蹴而就的,设定目标,规划出达成路径以后,应该在平时多积累,多学习,日拱一卒,才有可能实现。
  • 以终为始,工作以后感觉时间变得特别宝贵,尤其是大脑活跃,效率较高的时段。拿考雅思这件事说,一开始就没有制定明确的目标,只是盲目的复习,花了很多时间,最后收效甚微。感觉这已经是我的一个缺点了,在高中开始就这样,没有考虑目标是什么,怎么实现最为高效,而是花笨功夫去磨。以后做事还是应该定好规划,研究好方案再行动。
  • 把欲望放到台面上,前两年是带着自己的性格走入职场的,而且学生气太重,不敢表达诉求,不敢正面沟通,不懂得向上管理,这会造成自己工作很努力,但是由于性格原因,导致吃亏。以后要多长个心眼,理解老板的想法,做到方向一致很重要。

带爸爸北京玩一周

国庆前一段时间和高中汪同学聚了一下,聊到带父母旅游这件事,突然觉得国庆就是个机会,本希望爸妈一起来北京的,但是妈妈非常抵触出远门,而且也还在上班,所以就只有老爸来北京。国庆期间很多场馆的票很难预定,比如军事博物馆、国家博物馆提前几天都订不到。

  • Day1 毛主席纪念堂、天安门广场、故宫、景山公园
  • Day2 中国科学技术馆、奥林匹克森林公园、天坛
  • Day3 北京大学、颐和园、
  • Day4 八达岭长城
  • Day4 中国博物馆

虽然一开始老爸一直说农忙,来了也花钱,但感觉他玩得蛮开心的,在长城上下那些陡峭的台阶像个十几岁的少年一样,脚下生风。

以后想多带他们走走,多看看这个世界,多一些有刻意留念的时光。

吃喝玩乐那些事

  • 爬山,今年大半的时间都在考雅思,所以爬山参与比较少,就组队去了三峰和摘柿子。
  • 旅游,五月去了青岛,普通意义上的旅游。
  • 今年尝试了滑雪,第一次就驾驭了双板,下次要尝试双板。
  • 今年感觉每周都会出去吃,大虾、烧烤、火锅、烤鱼,感觉现在吃饭都有点挑食了。

2023已经过去了,有得有失,总的来说,还是有进步的。2024,自律起来,成为更好的自己。


读《人间词话》精选十句

落日照大旗,马鸣风萧萧。

细雨鱼儿出,微风燕子斜。

一点浩然气,千里快哉风。

细雨湿流光,芳草年年与恨长。

沙上并禽池上瞑,云破月来花弄影。

可堪孤馆闭春寒,杜鹃声里斜阳暮。

昨夜西风凋碧树。独上高楼,望尽天涯路。

寒波澹澹起,白鸟悠悠下。怀归人自急,物态本闲暇。

自在飞花轻似梦,无边丝雨细如愁,宝帘闲挂小银钩。

浮生长恨欢娱少,肯爱千金轻一笑。为君持酒劝斜阳,且向花间留晚照。


关于尝试微软

先说一下结果,面试了四轮,最后挂掉了。

是找微软HR内推的,很快就收到了phone screen的邀请。phone screen是一轮初步面试,分为项目经理和算法两个部分。算法题是编程计算在一个有障碍物的矩阵中,1表示障碍,0表示可以通过,计算机器人从左上角到右下角的路径总数。用回溯方法做出来了,面试官要求用动态规划,带点小bug也勉强做出来了。第一轮面试反馈不错,这一轮通过才能继续后面的面试。

接下来是连续三轮面试。

第一轮是其他部门的同事,问题比较发散,对网络通信和加密比较感兴趣,算法题是给定两个下标,编程交换一个链表中的两个值,值类型为范型。比较简单的一个题,没多想就去遍历,做出来的结果被提醒需要优化。感觉给面试官印象很糟糕,越简单越搞不好。

第二轮应该是本部门的了,全程笑呵呵的,答得怎么样也不反馈,就像是聊天一样。问题也很发散,算法题是字母a-zA-Z分别映射为1-52这些数字,现在给定一个数字串,编程计算有多少种把数字反射为字母的组合。同样可以使用动态规划计算,需要注意第二位为0的情况。面试结束最后祝我好运,感觉这一轮要挂了。

第三轮是engineer manager面试,英语问题比较多,一度听不懂。问题还是很发散,Android的MVP和MVVM架构、数据库join方式和职业规划都聊。算法题是指定缓存容量,实现LRU缓存。做了半年的题,就只有这个之前做过。

面完感觉有机会,但是又感觉很悬。周末两天埋头刷剧等消息,but今天早上等到了面试挂掉的邮件。

估计今年就继续苟着了,反思下这次面试的过程。

  • 平时工作不太注意总结,只是想着完成任务。但是面试过程其实是是关注解决问题全过程和提取到的方法论的。平时不总结梳理,回答问题的时候由于别人不了解业务背景,所以总让人不知所云。
  • 简单问题不要着急,想清楚再回答,简单的问题还出错,只能是减分了。比如这次面试一时口误居然回答使用hash算法进行通信加密,话一出口就想给自己一个大嘴巴。
  • 对一些常见问题再面试之前应该提前预备答案,比如为什么想尝试这个职位、工作中最激动和最沮丧分别是什么时候,这些问题通常是leader面会出现,答不好感觉就会造成价值观不匹配的印象。
  • 工作领域的东西还是要一项一项抽时间学会,不管目前工作需不需要。比如MVVM、MVP、Binder。

结果挺让人沮丧的,这半年又是刷题又是撸项目的,最后半个水花都不起,今年大概不尝试了,继续搬砖。


rxJava

[TOC]

RxJava是ReactiveX在JVM上的实现,ReactiveX可以利用可观察序列和LINQ风格查询操作符来编写异步和基于时间的程序。使用Rx可以通过Observables表示异步数据流,使用LINQ操作符查询异步数据流,用Schedulers参数化异步数据流的处理。可以理解为Rx结合了观察者模式、迭代器模式和函数式编程的特点。

Observable

可以理解为观察者模式中的被观察者,会异步的发出事件序列,比如网络请求或者IO等,都可以封装为一个Observable。RxJava将很多Rx提供的操作符实现为了函数,可以通过这些函数对Observable发射出的数据进行操作,继续返回一个Observable对象,这些操作函数可以简化对Observable对象的处理。

Observable类型

  • Flowable,支持背压,当观察者处理发射数据处理不完时,可以执行一些策略,比如抛出错误或者丢弃一些数据。
  • Single,只发射一个数据或者错误通知。
  • Observable,可以发射不确定数量的数据。
  • Maybe,可能发射一个或者不发射数据。
  • Completable,用于Observable在完成某件事不发射数据时。

常用操作符

  • create 用于创建一个Observable,给这个操作符传递一个接收观察者作为参数的函数,编写这个函数让它的行为表现为一个Observable–恰当的调用观察者的onNext,onError和onCompleted方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    private static Observable createFirstObservable() {
    return Observable.create(new OnSubscribe<Integer>() {

    @Override
    public void call(Subscriber<? super Integer> subscriber) {
    try {
    if (!subscriber.isUnsubscribed()) {
    for (int i = 0; i < 5; i++) {
    subscriber.onNext(i);
    }
    subscriber.onCompleted();
    }
    } catch (Exception e) {
    subscriber.onError(e);
    }
    }
    });
    }
  • 将其他种类的对象和数据类型转换为一个Observable, 可以转换Future、Iterable和数组,产生的Observable会发射Iterable或数组的每一项数据。

    1
    2
    3
    4
    5
    private static Observable createObservableByFrom(){
    Integer[]data = {0,1,2,4,6,8};
    Observable observable = Observable.from(data);
    return observable;
    }
  • just操作符将单个数据转换为发射那个数据的Observable,与from不同,just不会取出数组或者Iterable中的数据逐个发射,而是一整个发射。如下面代码所示,如果我们输出Observable的数据项的size,输出为2,可见是把这个List作为一个数据项。

    1
    2
    3
    4
    5
    6
    7
    8
    private static Observable createObservableByJust() {
    Student s1 = new Student("zhu", 22);
    Student s2 = new Student("long", 34);
    List<Student> data = new ArrayList<>();
    data.add(s1);
    data.add(s2);
    return Observable.just(data);
    }
  • map操作符,接收一个转换方法,对Observable发射的数据进行映射操作,返回一个转换以后的Observable。

    1
    2
    3
    4
    5
    6
    .map(new Func1() {
    @Override
    public Object call(Object o) {
    return ((Integer)o)+1;
    }
    })
  • distinct只允许发射还没有发射过的数据。RxJava将此操作符实现为一个函数,接收一个函数,此函数返回值为区分数据的key。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    private static Observable<Student> createObservableByFromForDistinct() {
    Student s1 = new Student("zhu", 22);
    Student s2 = new Student("long", 34);
    Student s3 = new Student("zhu", 34);
    List<Student> data = new ArrayList<>();
    data.add(s1);
    data.add(s2);
    data.add(s3);
    return Observable.from(data);
    }
    public static void main(String[] args) {
    createObservableByFromForDistinct().distinct(new Func1<Student, Object>() {
    @Override
    public Object call(Student student) {
    return student.name;
    }
    }).subscribe(new Action1() {
    @Override
    public void call(Object o) {
    System.out.println(o.toString());
    }
    });
    }

    Student{name=’zhu’, age=’22’}
    Student{name=’long’, age=’34’}
    onComplete

  • 其他操作符,filter用于对Observable发射的数据进行过来,接收的参数为一个谓词测试语句,只发射通过测试的数据;take操作符可以发送前面N项数据,忽略后面的数据。

observeOn

指定一个观察者在哪个调度器上观察这个Observable,这个观察者的onNext、OnCompleted和onError方法会在指定类型线程运行。

subscribe

操作符是连接观察者和Observable的胶水。一个观察者要想看到Observable发射的数据项,或者想要从Observable获取错误和完成通知,它首先必须使用这个操作符订阅那个Observable。这个方法接收三个方法或者实现了这三个方法的接口的对象。onNext在Observable发射一条数据时调用;OnError, Observable调用这个方法表示它无法生成期待的数据或者遇到了其它错误; onCompleted,如果没有遇到任何错误,Observable在最后一次调用onCompleted之后会调用这个方法。subscribe也可以接受一到三个函数,分别解释为:

  • onNext
  • onNext和onError
  • onNext, onError和onCompleted

subscribeOn

指定Observable自身在哪个调度器执行。


fail-fast and fail-safe

Fail-fast And fail-safe

我们可以通过迭代器遍历集合对象,迭代器分为fail-fast和fail-safe两种类型,fail-fast是指当我们通过迭代器遍历集合时,如果集合元素发生了修改,会抛出ConcurrentModificationException异常。fail-safe类迭代器则不会抛出这类异常。

Fail-fast case

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
ArrayList<Integer>data = new ArrayList<>();
data.add(1);
data.add(2);
data.add(3);
Iterator<Integer> ptr = data.iterator();
while (ptr.hasNext()){
Integer a = ptr.next();
System.out.println(a);
data.remove(a);
}
}

执行如上代码,在通过迭代器遍历集合时,对集合进行修改,删除了一个元素,迭代器在遍历时会抛出如下ConcurrentModificationException异常:

结果

Fail-Fast Iterators internal working

以ArrayList为例,分析fail-fast的原理。ArrayList有一个迭代器内部类 ListItr, 我们在通过iterator()方法返回ArrayList对象的迭代器时就是返回这个类的一个实例。

1
2
3
public Iterator<E> iterator() {
return new Itr();
}

内部类 ListItr 有一个属性expectedModCount,在创建 ListItr 实例时会赋值为modCount,而modCount则是在构建迭代器之前当前ArrayList实例的修改次数,当对ArrayList对象进行修改时,modCount的值都会加1。内部类 ListItr 有一个内部方法:

1
2
3
4
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

在通过迭代器调用next, add, remove, set等方法时,首先会执行如上checkForComodification方法,如果在这之前集合对象发生了修改,那modCount的值增加以后,将会执行if语句中泡出异常的操作。

但是如果我们使用内部类ListItr自身提供的修改方法,则不会抛出ConcurrentModificationException异常,因为这些方法实现会更新expectedModCount的值。

Fail-safe case

和fail-fast的迭代器不同,fail-safe类迭代器遍历集合时,如果对集合进行修改,会拷贝一份集合元素的副本,在副本上进行修改,所以不会抛出异常。以CopyOnWriteArrayList为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
List<Integer> data = new CopyOnWriteArrayList<>();
data.add(1);
data.add(2);
data.add(3);
Iterator<Integer>iterator = data.iterator();
while (iterator.hasNext()){
Integer a = iterator.next();
if (a==2){
data.add(4); //modify while traverse over the collection.
}
System.out.print(String.valueOf(a)+' ');
}
}

运行结果:

结果

即时我们在遍历过程中对集合进行了添加元素,也不会体现在遍历结果中,因为是在另一个副本中进行添加的。

Fail-safe internal working

看一下是如何对副本进行操作的,以add方法为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray(); //return array, which store the element.
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); //array length incremented to original length + 1, and copy the original elements to newElement array.
newElements[len] = e; //the last solt is e, which we want to add.
setArray(newElements); //set the original array reference to newElements.
return true;
} finally {
lock.unlock();
}
}

可见添加元素是重新开辟了一份数组空间,添加了元素之后再修改数组引用。CopyOnWriteArrayList的迭代器遍历的却是原来的数组:

1
2
3
4
5
6
7
8
9
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0); //getArray() return the original array reference.
}
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
//traverse the array by snapshot, the snapshot is the original array. elements may receive new array
//value, so we need use snapshot to store its reference.
snapshot = elements;
}

显然,使用CopyOnWriteArrayList的缺点也是明显的,首先,由于在副本上进行修改操作,遍历其上的迭代器不能反映出其最新的状态,其次,需要一份额外的内存空间,还需要对元素进行拷贝迁移,这也是很耗费性能的,所以应该尽量在读多写上的场景进行使用。

参考文献

Fail-fast and Fail-safe iterations in Java Collections


RN开发小结

RN开发小结

什么是RN

React Native是Facebook基于React 开源的移动端开发框架,React是一个通过声名式UI构建组件的JavaScript库,React Native结合了原生的流畅和React的特性,是目前比较流行的移动端开发框架之一。通过声明组件,组合这些组件,完成App开发的复杂功能,这些组件虽然都是使用JavaScript开发,但是最终渲染都会使用原生API,所以渲染的流畅程度并不会太差。

为什么使用RN

  • RN具备跨端的特性,目前支持iOS和Android。目前大多数公司的移动端应用都会在iOS和Android两个平台上同时开发,开发一个feature或者修复一个bug,通常需要两个开发人员。使用React Native,开发的程序可以兼容两个平台,虽然有一些差异需要针对特定平台处理,但是还是可以节约一部分开发人力。
  • 灵活更新,不需要等待发版。使用React Native开发的程序,会被打成bundle,供用户下载使用,不需要等待发版周期,这对于一些紧急需求是非常必要的。而且对于iOS的应用,很多时候应用商店会审核不过,导致用户很难体验到新的功能。一些线上bug可能需要发布热更新或者只能等待下一版本修复,但是React Native可以灵活修复,修复版本可以覆盖几乎所有用户。
  • 对于开发人员来说,React Native基本上实现了所见即所得,一次修改,快速检验效果,这相比native开发所需要的漫长打包时间,大大提升了开发体验。

Key Tech

React Native通过声明式UI开发组件,然后组合组件开发出更加复杂的组件。组件分为函数式组件和类组件,函数式组件是无状态组件,类组件具备自己的状态和生命周期方法,后来为了减少类组件中各种生命周期方法的样板代码和复用状态的困难,提供了Hook,使得可以使用函数组件使用state和生命周期等特性。

  • useState,这是一个hook,我们可以通过它为组件存储一个状态,并在适当的时机修改它。对于一般的变量,函数退出后它就会消失,但是对于useState定义的变量,会被保留。

  • useEffect,可以将其看作以下三个函数的组合,当组件渲染完成以后,我们可能需要执行一些操作,那我们可以将这些操作放到useEffect中调用,useEffect会保存传递的函数,在每次渲染以后调用。有一些操作是需要在组件unmount之后删除的,防止内存泄露,可在useEffect的return语句中返回,进行删除。

    1
    2
    3
    componentDidMount() {}  
    componentDidUpdate(){}
    componentWillUnmount(){}

    1
    2
    3
    4
    5
    useEffect(() => {
    // Have a timer call the function every 5 seconds using setInterval
    const timer = setInterval(() => {}, 5000);
    return () => clearInterval(timer);
    }, []);
  • useRef可以用来返回一个可变的ref对象,对象的改变不会触发组件的重新渲染,在整个组件生命周期内是唯一的。

  • useMemo,useMemo返回类型不限的值,只有当依赖项变化时才会触发重新计算,

  • useCallBack,返回可被记忆的回调,每次依赖项改变时,都能生成新的回调。

踩坑记录

1、在实现两个组件重叠的效果中,往往需要使用绝对定位。使用绝对定位的的组件会脱离文档流,以最近的父布局作为参考,决定位置。但是这必须要指定其自身的高度或者宽度。

2、使用绝对定位有可能使组件被置于其他组件之下,导致点击事件被屏蔽,可以通过动态修改pointer-events属性来决定是否拦截点击事件。

参考文献