邢爱明

邢爱明 查看完整档案

郑州编辑上海理工大学  |  机械设计与制造 编辑郑州宇通客车股份有限公司  |  开发主管 编辑 xingaiming.com 编辑
编辑

会点sap和java开发,oracle也研究过一阵子,目前对hadoop和openstack比较感兴趣。

个人动态

邢爱明 收藏了文章 · 2020-03-01

风物长宜放眼量,人间正道是沧桑 - 一位北美 IT 技术人破局

引言

我对于本科时光的印象,还停留在那所普通 211 大学的建筑物之间,我坐在大学的时光长廊里,满眼望去,都是经历的过的故事。可毕业后回首,却很少有人能说,自己从来没有迷茫过。迷茫,仿佛就是一团乌云,笼罩在每一个心中怀有抱负的人的头上。每当夜深人静,思绪归于对自己人生未来的严肃思考,不知去往何处的苦闷,再加之不断迫近的升学/就业选择的压力,尤其是一些看似周密的计划,由于想把每一环都做的尽善尽美,往往不仅减少了反馈(一切的目标、报偿都在最后)、还因为人生的不确定性而很容易失败:以保研为例,我常常见到一些平常学习认真、热心参加学术活动,但是不关心所在院系制度的人,到保研时因为一些硬性规定(如必须完成某些并不喜欢的所谓保研必修课)、或者是不公平的排名标准(比如说活动成绩、竞赛成绩计算很高,导致成绩上产生的差距在计算时几乎没什么权重)导致原本计划了几年的保研机会失之交臂,而此时离考研也已不远,若无提前准备,也往往容易落榜。在这样的过程中,亲历者绝望、愤懑甚至因而抑郁的,我都见过,以至于都有些麻木了:太阳照常升起,一个人的悲哀苦痛,回到这个宏大的时代与社会中,连一粒尘埃也算不上;而那些看多了这样的故事的人,也难免兔死狐悲,故而更难聚集其一股欲与天公试比高的拼搏的意志。

2019年7月29日,《中国青年报》刊发《大学生抑郁症发病率逐年攀升 大一和大三高发》引发读者广泛关注。有31.2万网民参与了中国青年报微博发起的 大学生抑郁症发病率逐年攀升,你觉得自己有抑郁倾向吗的网络投票,其中认为自己有抑郁倾向且情况很严重的达到了8.6万,占比27.6%,若是统计为有过低沉的倾向,则有约18万人,占比为60.8%。

一些市面上的流行语录,似乎也从侧面说出了这种感觉:什么我所得到的不过是侥幸,纵然得到了一时的世俗意义上的成功,由于从来没有对自己的人生命运有一个通盘的规划与目标,即使通过搜集到的一些信息、经验走了一些捷径,但是短暂的兴奋后,却又会重新回到迷茫与空虚中来。我忍不住还是要发问:上了好学校、找了好工作、赚了不少钱,那然后呢?倘若有一步,没能像这样环环相扣地被达成,那又该怎么办呢?纯粹的人往往能在一个方向做出不俗的成绩,可过于纯粹,就难以承受突如其来的打击,难以接受自己的规划被命运玩弄,付之东流。

回想我的大学生活,也的确是这样:一起朝夕相处的同学朋友们,虽然可以一起感慨未来的未知与自身的无力,可自己的命运最终还是只能自己把握,总要面对那条只能一个人走完的路。而在我在大学期间认识的大约几百人里,真正能有坚定的三观信仰,又努力去做的(即所谓知行合一的),实在是凤毛麟角,而且这往往出自他们不断地痛苦地思考与试错,有时甚至还需要一点运气。

人本应该是越长大越坚强越成熟的,可在大学期间因迷茫和各种诱惑堕落的大多数人,其心志能力,往往连高中时代都不如,既失去了当初的纯粹与坚定,又没有真正获得一些面对问题、解决问题的勇气与能力。这种普遍的迷茫,不只是存在于那些混吃等死的人中,我认识的无数的名列前茅、努力拼搏的同学,也还是深受其苦。笔者也是前两年,才逐渐开始想明白。像这样大环境的精神空虚与迷茫,究竟是谁的责任呢?

毕业晚会时,一曲谁的青春不迷茫唱出了我的心声,歌词非常写实,也写出了很多人的青春,可是让我有些近乎条件反射般的讨厌:谁的青春又想迷茫呢?

一个人、一小群人的迷茫,尚且可以认为是个人心理问题,抑或是环境,甚至是遗传;可成批的学生都怀疑人生、看不到未来的出路、不知道自己要干什么,这究竟是谁的责任呢?辗转反侧后我觉得需要用文字来阐述一下我的观点。

那么究竟为什么会变成这样呢?就有没有什么合理的办法、科学的想法可以借鉴呢?作为个人,我们是不是也应该参考古今中外的真正的大人物、向那些慧人借鉴呢?

我的青春并不想迷茫,我相信大家的青春也都不想迷茫。虽然因为运气我有了一点成绩,但是我觉得这不少都得归于时运的赐予,不把这样的经验分享给更多还在痛苦思考中继续前行的人,我无法获得良心的安宁。

本文将从成因开始着手分析迷茫这个问题,从问题的产生到表现、再到教育制度、人性的缺点、再到我们可以锻炼的能力以及可以采取的想法(由于笔者也算是半个做技术的,相关的能力将主要以技术为基础)。

迷茫问题的定义与分析

在报刊或者一些网络评论中,偶尔也会看到老一代的人批判现在的年轻人事多,是的,当代的大学生,与过去的时代,是有一些不同的。

1993年,全国取消了粮票制度,这也意味着,这一代人基本是没有经历过饥饿与那种迫切的生存的问题。

相反的,更多的人考虑的是我活着有什么意义,表现在流行文化上,就是讲求个性,讲究表现自我,想寻找不同寻常的意义,而不只是活着。因此带来了更加自由的发展,以及求而不得的各种迷茫。

不过,为什么又一定是大学生呢?中学生的阶段、我们并不是没有其他想法,但是的确就没有那种难忍的迷茫。

因为对于大多数普通学生而言,大学以前的内驱力是很明显的。

由于中国绝大多数中学都在以高考成绩作为其神圣而不可动摇的目标,一系列神奇的口号也能体现这一点,譬如某些中学曾经打出的没有高考,你拼得过官/富二代吗?提高一分,干掉千人!不苦不累,高三无味;不拼不搏,等于白活!等等。

从现实的功利,到亢奋的鸡血,到想压人一头的野心,这些口号就是这种高考文化的最好体现。

从报刊书籍到街谈巷议,从学校老师到家长学生,能不受这种文化影响的,很少很少,或多或少都要受此裹挟。

所以一切的其他梦想并没有被真的解决了,而是以一句你高考考好了,未来都能实现的,其实是某些落榜生的绝望,也是受到了这句话的影响:那我要考不好,我的人生是不是就废了?

将一次考试完全的神化,对于人的长期热情,实际上起到了杀鸡取卵的作用。

因为这种热情,随着高考的结束,在大多数人身上重新消失了(既然高考都是所谓的最后一战了,之后岂不是就应该安享人生了?)。

而且往往因为此前压力太大,还会引起加倍的反弹,究其原因,还是因为大学没有了那种统一的价值标杆,正如一句近年来很火的评论高中忙的理所当然,大学忙的莫名其妙(说一句题外话,这句话原本是我一个大学同学随手写的说说,写完当晚转发破万)。

即使是清华北大,年年都有人因为沉迷游戏退学的,至于我自己的所见所闻,沉迷游戏的人,甚至可能不止百分之二十,每个年级因严重沉迷而退学的,也屡见不鲜。

严重者如此,症状较浅者因为没有目标而沉迷游戏,透支自我,意志消沉的,那就不计其数了。人生尚有许多美好故事可以经历,而他们却早早地选择了这样的透支

不管怎么样,随着高考的结束,这种受全社会标榜的奋斗价值仿佛就告一段落了一样。一群被社会舆论、集体主义裹挟着完成了高考的人,却一下子被投入了一个多元化价值的环境里,什么都好像是有意义的,什么又都好像是没有意义的,那种确定性的价值的丧失,让人产生了巨大的恐惧与不适应。

这就是迷茫的定义了:由于价值多元化,不能找到并坚守属于自己信仰的价值并坚定决心的去追求

这种迷茫的弥漫与试图消解,也构成了当代文化的一个大类,或者与之交织起来,形成了更深层的恶性循环。

读书

被认为最好的方法一般来说是多读书。可即使是读书,如果读不到一定深度,往往也并没有什么特别的价值;即使是似乎对对口解决这个问题的哲学,其最新的发展,也就是现代西方哲学,依旧有一个重要的问题,那就是虚无主义

西方的主流精神史说来也好笑,过去的几千年一直迷信上帝(或者神及其等价物),一切价值都可以从那里推出来;

然后到文艺复兴与启蒙运动,短暂地迷信了一会人类理性的作用;

结果因为理性的滥用,尤其是试图用理性来论证人生的意义,就走到了问题的反面——人生的意义不是浅薄的理性可以论证的,所以尼采说上帝死了(狂热的信仰神的意义是不能用逻辑论证的)。

这就是现代西方哲学让人陷入的所谓精神荒漠,凡是想要真诚研究探讨这些问题的,就越是觉得人生没有意义。

至于其他种类的书籍,学心理学本指望能搞清楚自己为什么抑郁,结果一堆又大又空洞的人格模型性格模型之类的名词扔来,解释什么内容都好像有点道理,但是想更直接的使用分析具体问题,发现总归又差了点,只看书肯定是分析不了的;

学社会学本指望能搞清楚社会如何运行,结果大杂烩似的讲一堆似是而非的社会建构理论,然后分析各种人群的特性,感觉明白了好多东西,但是这跟我应该做什么又有什么关系呢?

至于经济学、政治学,都是讲了一堆有时都不一定能自圆其说的道理,且不提理解上往往难度较高,即使真的理解了,也很少存在什么让人能一定要坚持的道理。

虽然口头上和心里觉得很多理论是不错,但是为了这些抽象的信念而献身,似乎又并没有那么强的内驱力,而且执行起来也不知从何着手。

是的,市面上大多数的关于读书的宣传,往往也就只能把书读到这个程度了,读书当然不是坏事,浅阅读才是,所谓浅阅读,就是不能将书籍与自己的思考、经验、对社会实际的认知结合,所以有一句流行语就反映了对这样的理论的嘲讽:有什么卵用呢?是啊,什么东西只知道这么肤浅的内容,看着样样都知道,其实呢:学了金融学也不懂金融体系的运行,学了投资学炒股还是一样亏本,看了社会学经济学,问他为什么现在房价这么高、中国国民经济的体系结构究竟是怎么样的,未来将会发展的产业又是什么……不用问,只是读到上面那种程度,是根本不可能答得出这些问题的。

可是,读书并不是目的本身,很多名著也不是为了写作而写作的,马克思写《资本论》,是为了分析清楚在资本主义条件下,社会的各种成员从商品到企业再到产业的价值分配问题,最终揭示整个经济体系运作的机理,从而教育劳动者为了自己的合理利益而斗争。

而很多营销号,就往往只会售卖这些廉价的杂学知识、小技巧、人生社会的一些或者正确而无用的废话,很少能真的讲出一些具有可操作性的深度思,而这是古往今来一切有价值的好书的共性:好书不仅仅是议论本身,它们本质上都是作者在自己认识世界、改造世界的过程中积累而成的经验,由于作者的时空所限、因为文字本身表达的模糊性所限,自然而然会出现老子所谓道可道,非常道(真理如果能被文字表达出来,那么就不会是永久的真理)的现象。而判断什么对、什么不对,都需要读书者自己努力思考、在社会中进行实践。知易行难,这就是肤浅的、不去把一些似是而非的东西真正考证清楚的书籍、学问往往真的没什么卵用的本质。

俗世标准的成功门槛太单一

回想我的中学时代(这种县中的应试教育模式,应该也能让很多人同样想起自己的中学时代吧)

每天我们会被要求早上六点五十之前到校,然后开始早读,并开始一天的灌输性的课程,然后完成每门课要求的各种作业、试卷。

其中主要的创造性、思考性的工作,可能也就在跟老师一起思考问题以及做作业中了,至于课外的内容,即使不是完全不涉及,能花的时间也是很少的。

由此带来的结果,就是虽然获得了不错的高考成绩,考上了不错的大学,但是上了大学后却发现学不进去了很不适应新的上课方法,因为没有那么多针对很细节的知识点都做出来的广大的题库了(高中时,即使是很小的知识点,肯定当晚的作业习题就会做个好几题),而自己把一个个知识点弄得条理分明的能力,并没有得到过有针对性的锻炼。

大量的学生,即使认真学习,也不知从何下手,往往到大二大三,可能才渐渐适应大学的课程松散而作业不多的情况。 但是,很多人上了大学之后,也还是摆脱不了这种鸡血的生活,强迫症似的逼自己变得优秀,一心追求更高的分数、更多的竞赛等等与高中时代别无二致的评价体系中的成功

再演变下去,那就是更好的研究生名额、工作,更高的薪水......

那再然后呢?为什么一定要这样?比别人好就行了吗

于是在学会反思之后,很多人也不能摆脱这样的迷茫:这样做自然不会让自己变的很坏,可是离那种指点江山、意气风发的少年英雄的形象,似乎也实在是看不到什么接点,活着难道就是为了时时刻刻在这种标准中比别人好那么一点吗?

而且最重要的是,不像谁都会关心你的高考一样,上了大学之后,除了父母,几乎真的没什么人会很关心的你的成绩如何了,于是很多人渐渐选择了退出这种竞争。

当然,除此以外也可以追求一些别的成功:去做各种各样的活动、志愿者等等,除了少数真的明白自己在做什么的人,大多数人也还是多了些经历、少了些激情,因为没有明确的目标指引,没有明确的意义的驱动,这些平庸的优秀实在不能让人满意。

表现在工作上,也就是近来被人批判的所谓奋斗逼,不是奋斗不对,是为了奋斗而奋斗,实在是很可悲的。

可是,人之所以高贵于动物,在于人会反思、会追问意义,如果一个人被突然要求像西西弗斯一样,每天把一个大石头推上山、一旦到了山上就推下去,于是周而复始,而没有一个明确的目标与意义,无论是多么坚定的人,都难免发问:像这样活着,有什么意义

现代很多男性,为物欲与社会的虚名浮利所迷,觉得只需要迎合所谓丈母娘的爱好,有一个好工作(或者年薪够高)、有车有房,人生好像就圆满了,于是人的价值几乎就只剩下了钱,若是碰巧得到,方还好,可欲壑难填,赚了这么多,总是会一山望着一山高,永远得不到真正的意义。而且,不客气的说一句,像这样的成功男士,除非运气极佳,大多难守那么多的财富,也很难获得真正的精神上的完满。当他有一天开始反问自己:像这样活着,我为了什么?此时就是所谓中年精神危机的开始了。

追求世俗的成功并没有错,但是单纯追求别人口中的成功,不过是把自己信仰的主宰权交给别人,而一个有独立意志的人(在古书中往往就是所谓君子、大丈夫之类)是绝不应该满足于此的。就像作为一个刻板印象出现的程序员群体,就是这种化身:钱多话少死的早,除了像机器一样被劳动异化为非人(实际上没有独立意志的人),什么东西都交给别人、社会去评判、去决定,自己心中没有对于真爱、真理、正义的绝不放下的那种热情与信念。像这样的人,哪怕年纪很大,也不过是一个大男孩罢了,甚至用这些年更火的词来说,就是巨婴

古时的君子、英雄,为了改变他们眼中不合理、不完美的世界,投入各种变革、忍受千难万险、不断提高工作能力,他们清醒地认识到自己的能力的界限,只是耐心地、智慧地、一点一滴地往那个目标前进,如果有必要,他们还会舍生取义,这是因为在他们的生命中,有比他们的生命乃至一切都更重要的东西。而大男孩们,平常嘻嘻哈哈,遇到风浪,却不能挺得住各种打击,为自己理应守护的人或者理念战斗到底,这与所谓仗义每从屠狗辈,读书多是负心人颇有些不谋而合。这样的人只要能获得一些钱财,就可以赞美为成功,甚至飘飘然自以为大丈夫,不亦悲乎?

享受

那,既不想每天过的那么劳劳碌碌,也不去读书,如何呢?

自然更是不行。

为什么?

现代社交媒体、信息平台太多了,一个年轻人至少可以从微博、微信公众号文章、今日头条类的新闻推送工具、知乎等APP(我认为99%的人至少用过上述一种或几种)上获取信息,这些信息还是会潜移默化的教育人,让人自然而然形成对人生意义,或者再不济,什么是成功的思考。

由于执笔者自身往往局限性就很大,往往不过是一无所成的白面书生,所以写出来的文字往往也根本没有清晰的思想,只是迎合各种欲望和热点,或者空洞地谈论一些自己根本不能践行到底的思想。 所以阅读这些文字的人,潜移默化受其影响。

故而得不到自己的意义,就觉得自己很失败,又不知有什么路可以走,若是心气不够高、能力又不足,就容易放弃治疗(放弃有条理的努力),变得很(觉得人生无意义又无可奈何,只能通过嘲笑自己与人生的荒谬而活下去)。

每个看似荒谬的社会现象的背后,总有这种其实让人心酸的绝望,如果不是没办法、找不到出路,谁又想那么丧呢?

此外,消费主义文化在资本的驱动下,时时刻刻在想办法侵蚀年轻人,抖音、快手之类的短视频,兜售口红、美食等等所谓精致生活。(校园贷之类的在较好的大学还不算太猖獗,但是借花呗严重提高不必要的消费还是家常便饭)

其实就是把人需要的几种欲望包装一下:食欲(网红美食、奶茶)甚至性欲(各种小姐姐短视频的本质往往不过是软色情)。

这些无所谓的东西,如果大量花时间沉迷其中,不仅消磨了人的时间,更是腐化了人的斗志,而且假借时尚与娱乐的名义,让一切对于它们的严肃批判都显得很反潮流

虽然它或许是短期见效最快的缓解迷茫的办法,但是长远来看,这也是危害最大的方法,不过饮食宴乐,谁又能说一点不沾呢,适度的话,也有利于放松身心。

更重要的是,它们降低了人们获取快感、反馈的阈值,让人很容易就想葛优躺,只想躺着获得直接的快感,从而渐渐陷入了一个难以振作的怪圈之中,导致所谓的行为成瘾,从精神到肉体都逐渐萎靡,身材走样还是小事,毕竟还能健身改变,养成了这样散漫乏力的精神习惯,志趣也便越来越卑下猥琐,要知道,人是一种很容易文过饰非的动物,一旦人开始不断地为自己的不作为寻找借口,比如说拖延症、比如说努力也是一种才能,我没有才能等等,我看很少有能真的治好的:反正我都有病了,你们就别指责我了,让我爱咋咋地吧!

波伏娃曾经评论过:男人的极大幸运在于,他,不论在成年还是在小时候,必须踏上一条极为艰苦的道路,不过这是一条最可靠的道路;女人的不幸则在于被几乎不可抗拒的诱惑包围着;她不被要求奋发向上,只被鼓励滑下去到达极乐。当她发觉自己被海市蜃楼愚弄时,已经为时太晚,她的力量在失败的冒险中已被耗尽

她是用这句话讽刺欧洲的女性被社会文化鼓励不去努力工作、开拓事业的,但是如果把主语的男人、女人,改换成不迷茫的人与迷茫的人,或许也并没有太大语病。

小结

以上三者,如果我们归结一下,有一个共同的本质:找不到人生的乐趣,因而产生不了对一些本值得热爱的东西的激情(即西方所谓passion)。

信念与热情之所以重要,就在于其对于人的近乎绝对的强制作用,它就像康德口中的道德律令一样,一切恶习(尤其是懒惰与萎靡)在它面前都不能成为借口。有拖延症?没时间?没精力?脑子不够聪明?没钱没地位?都不能构成一个理由。倘若真的觉得一件事物是对的,比如说正义,自然就得为其负上各种各样的责任与义务,不论时间、不论空间,不论正在从事什么工作,一旦有了机会条件,就一定会把自己的才能、经验、资源统统用在这上面,这就是所谓激情,也就是驱动人去完成一些不得不做的事情的本源。

人与人之间虽然可能的确有一些天赋、智商等等的差异,但是绝对没有想象中那么大,只是达到一个行业前 5%,都是完全可以通过努力解决的,所谓努力,不是说闷头搬砖,举一个极端的例子:当年轰动一时的暴走妈妈体重很胖,但是孩子手术需要移植她的器官,她就每天暴走二十几公里,恐怕她以前一个月都未必会运动这时一天这么多;对于很多需要智力的工作,如果真的到了非做不可的地步,查书、查资料、把能问的人、能用的资源统统用上,于是突破过去的眼界和能力,得到巨大的进步。而很多人呢,只是稍微遇到了一点挫折,就坐在地上,实在是没什么值得同情的:毕竟,什么英雄不是这样,一次一次超越自己的极限,才能获得最终的成功呢?

所谓的领导气质(leadership),我觉得也就是基于这样磅礴的热情:百折不挠,一定要完成心中的伟大目标,并引领后来人一起走上这样的道路,关心他人的发展,关心团队的进步,这才是一个合格的领导。当他走的是一条能为了全人类谋福利的大路,并能走在人们的前面,高举旗帜、披荆斩棘,他就是人民的领袖,就是历史的巨人。这样的人的伟名也终将流芳百世,当人们重新呼唤起这些名字时,他们会明白,这就是伟岸的巨人,他们将永远俯视那些匍匐着的卑下的灵魂,而面对他们的骨灰,高尚的人们将洒下热泪

对于教育制度的反思

大科学家钱学森曾经有过著名的钱学森之问:为什么我们的学校总是培养不出杰出的人才?

流行的说法是将这个问题理解为为何我国正规教育体系近年来没有培养出大师,但是,很多大师本来就出自个人的热情与努力,本身就不是能像模板一样培养出来的,而武汉大学前校长刘道玉对这个问题则认识颇为独到。他说,钱老的意思是,为什么我们的教育不能培养出真正的“创新型人才”?

什么是创新型人才?甚至说,什么是创新?我不敢直接对其下一个定义,但是以我个人的经验来看,所谓创新型人才,与我们一直提倡的素质教育以及真正的高等教育,至少具有包含的关系,这又如何理解呢?

现在的大学教育,自从大学扩招之后,一直是广受诟病的,一个概括性的形容词就是教育水平严重滑坡,简单来说,就是与扩招之前、与国际一流水平差距很大。

在这一部分,我将以个人经历为主(本科在国内、有美国交换生经历,研究生也在美国),从三个方面,梳理一下我对国内工科教育体系不足之处的思考以及关于个人、体制可以做的一些补救的措施的理解。

工科技术教学是否滑坡了?——与国外高校以及国内过去情况对比

从当前国内高校的教育体系来看,很多课程出现了这样的问题:教学分离、教考分离。本科期间我修了大概六十门课程,自学的课程数,恐怕不低于半数,大量的老师上课只能照本宣科或者略微做一点阐发,且不提什么融会贯通、讲讲实际在工程、科研中的应用了,大多数情况下,也就只能保证讲的东西不出错,至于说有没有更好的办法、如何从初学者的角度提出便于思考理解的方法,一般来说,大多数课程都做不到这个层次。

对于一门课程,我认为大致有三重境界:第一层是不出错,把需要讲述的课程以正确的形式展现其主干;第二层是一气呵成,老师能以自己的一套便于初学者理解的观点形成连贯、生动的讲解,在保证正确的前提下,使用多种多样的形式进行阐发,使得更多人能理解的更深刻清晰;第三层则是融会贯通,一个老师本身理应是这门课程相关领域的专家,那么自然应该有一些与其专家身份相称的高观点,更加深刻、灵活地阐述这些知识点在具体问题、尤其是一般人不能解决的问题上的合理应用,如何利用知识解决一些有实际意义的问题,这才是一门课理想的境地。

但是正如这个分类,很多老师还停留在第一阶段,连像高中老师一样达到第二阶段的都往往称得上名师教学能手了,这在原本应该是有大师的大学,真是一件奇怪而讽刺的事情。

与之完全不同的是,美国的工程教育则是世界有名的优秀。我举一个略极端的例子,我交换的美国一所州立旗舰大学,其学生数理基础平均水平,以国内高考衡量,应只有二本左右的水平(我曾不止一次在教他们题目时,遇到他们连高二左右的数学题都想不出来的情况,这个情况如果在我本科的学校,我可能已经直接骂了,而本校的新生,绝对会觉得不好意思而不是理所当然)。

但是只从大三左右的专业课谈起,对比州立大学的EE(电气工程)专业与我本科院校的自动化专业(我校王牌专业之一)的学生,能熟练使用 matlab 仿真一个电路/控制系统,并在单片机等类似硬件设备上实现的比例,显然是州立大学的学生远高于本校的学生。不仅如此,美国学生主动提出问题、解决问题的热情,更是远远高于国内。

因为对于州立大学来说,使用 matlab 仿真一个课程相关的内容,那往往只是一门课程的小作业要求(有时候作业的解释并不很详细),普通学生,不论是跟同学讨论、请教助教或者谷歌寻找相关的教程,还是会尽量自己独立完成作业的;而对于我们自动化的同学来说,这种东西只有大佬学霸才会,最多不超过百分之十的人会,往往就是拿着某几个大佬甚至是几届以上的代代传来应付作业(甚至还可以用一用万能的淘宝代做,这里就点到为止了,不能再传播这样教人学坏的方法了)。

那么跟国内以前比怎么样呢?以自动化专业的控制系列课程为例,对比的对象主要是目前我的本科学校自动化从工程数学、信号与系统到控制工程基础、现代控制理论,与过去哈工大一位老师大约十年前的一门《控制系统设计》课程(我只看到了那门课的一场 45 分钟的节选)。

如果是一个行家来分析我们的教学结果,可以说是有些可耻的:大多数学生学完了这几门课程了,由于上课时往往只是对着 ppt 或者课本讲几个例题(本质上只是算术题,连数学都不配称呼,因为缺少起码的技巧与思想),结果就是学生连傅里叶变换的内涵都不能理解,更不要说进一步推广到频谱分析等等应用上了,至于所谓课程之间的融会贯通,更是天方夜谭一般。

反观哈工大这位老师的 45 分钟的课程录像,他简单的从波特图(bode plot)入手,从高低频分别讲解了设计的要点,并以自己从事的课题飞行器设计为例,提到工作频率大约在 20Hz 左右,随后又讲解了截止频率,并与模拟电路上的运算放大器之所以存在所谓 1MHz 这样的提法无缝衔接。

这就叫深入浅出,不仅简明扼要地讲明白了问题的关键,还理清了各门课之间的关系,并能联系到工程实际。虽然这只是一门理论课,但是高下立判。

跟一些老教授探讨后,他们都表示,二三十年前的课程,基本都能达到上述的那种深入浅出的要求。这也从微观上印证了市面上流传的所谓大学扩招后教学质量的严重滑坡这一论断。

作为一个从事过科学研究类的脑力劳动工作的人,如果相信什么各种观点/事物都是各有所长,所以应该等量齐观/平等看待的观点,是可笑的,思想与境界就是有高下之分的,因为高观点可以包含低观点的内容,并解释说明其不能说明的内容

为什么会这样呢?

我们可以从世界名校斯坦福大学的成长历史来重新思考一下这个问题。

二战后,斯坦福大学为了合理使用多余的校园土地(并获得一定盈利),租出了大量的土地给高新产业公司使用,这就是后来大名鼎鼎的硅谷的雏形。

硅谷在大量人才、资本涌入后,几乎变成了全球高新技术的代名词;而斯坦福大学,也从那时一个建校才五十年左右的二流大学(当时哈佛、MIT 的获得的政府拨款都是上亿美元甚至十亿美元级别,斯坦福只有区区六百万美元拨款,还是用于培训教师的)一跃成为了如今世界上数一数二的顶尖大学。

为什么会这样?塔勒布在他的《反脆弱》(Anti-Fragile) 一书中提到:现代教育带来的最大的错觉就是,以为产业的发展是因为教育带动的,实际上恰恰相反,教育是由产业带动的。

1950年代,菲律宾的识字率是中国台湾的两倍,可从六十年代开始,(台湾也进行了所谓的十大建设)台湾的人均 GDP 以及各方面工业产业的发展,却远超过了菲律宾,这个优势一直持续到了今天(相反的,菲律宾大量的受教育人口,却带来了一个特殊产业—菲佣)。

科学研究或许不完全依照这个规律,但是工程技术的发展,几乎都是靠产业带动的。

举一个大家更熟悉的例子,华为在通信方面建树很大,它即使是给普通员工的薪资,在目前的就业环境中也是可观的。

因此当前在中国,电子及其相关的专业是一个很热门的专业,中国的通信技术也是世界领先的;

相比之下,生物学,生物虽然一度被吹嘘为所谓二十一世纪是生物的世纪,但是大量的中国生物毕业生,只能从事很低级的工作,薪资也很低,也便谈不上什么真正有意义的创新了。

同样的事情在美国则不然,生物信息学,由于有很多创业公司的投资,是美国名列前茅的高薪资工作专业之一,美国的生物技术发展,也的确走在世界前列。没有脱离产业的所谓先进科学。

无产业的所谓科学,往往不过是大量高影响因子的论文灌水,对国家甚至只是个人的发展,都是效益甚微的。

那么回到教育上,我们也可以类似地得出结论:没有脱离产业的所谓先进教学,一切不结合工程实际的教学目标与教学方法,基本上都是空谈,劳而无功,严重脱离实际,教学的实际效果也很差。

本文姑且不谈中国教育应该如何向这个方向改革,但是,个人在这样的教育环境下,应该树立怎样的发展自己的观点,还是值得探讨一下的。

刚刚来到大学时,我也遇到过各种各样很常见的问题:大学究竟该学点什么不同于高中的东西?我应该怎么适应大学这种较为松散的教学、工作环境?

另外当然就是顺便思考自己应该何去何从,也会经常在深夜辗转反侧,因为自己的无能为力而痛苦、愤怒,人最怕的不是辛苦,而是这种明知处于一种讨厌的状态,却无法摆脱的无能为力

对于第一个问题,在有了一些自学的经验之后,这时我也看了《智识分子》(作者万维刚,毕业于中科大,现为美国科罗拉多大学研究员)、《深度工作》(作者毕业于麻省理工,现为乔治城大学年轻的终身教授)等系统性总结学习工作方法的书籍,这才渐渐领悟了一个道理:

要想在各国现行高等教育下获得成功,或者更广泛一点地说,在当代越来越普遍的脑力劳动工作中取得成功,人必须得学会深度工作的能力(当然,也有人会把它通俗化说成所谓的学习能力)。

也就是说,学会针对特定的智力问题,摆脱外界打扰,刻苦钻研、自我学习分析的能力。这个能力是抽象的,但是也是具体的。

因为任何能每天专注工作思考自学很多个小时的人,对于大多数脑力劳动工作,都是可以很快掌握并熟练的,这就像一个大教授并不可能对一个新的研究方向感到完全束手无策一样,即使他可能以前从未了解过这个课题。

这种对自己学习研究能力的自信,我觉得就是高等教育或者说素质教育,理应培养的内容。

对于这个能力的培养,有一个小技巧:马克思说,商品的价值来自社会必要劳动时间,所谓必要时间,就是无差别的人类劳动;把这个观点推广到学习,或者更一般的脑力劳动上,就是要学会统计自己学习的有效劳动时间。大家应该都有过这样的经历:当看书或者做题很投入的时候,仿佛世界上只剩下了自己跟题目一样,全神贯注,用中国话说叫有点物我两忘,用现代的理论说,叫进入了心流的状态。在这种状态下的学习效率是很高的,可能一两个小时,抵得上漫无目的乱翻书几个小时甚至一天。因此,统计自己是否工作,就必须统计自己的有效的工作时间,一般看来,八到十个小时大约是普通人的极限了,再多下去基本上很难维持集中力(当然,根据每天的精神身体状态也会产生波动),所以如果做到比如说八个小时,就问心无愧地回去休息吧,一张一弛,文武之道,适当的休息才能保证后续工作的连续有效率。

当然了,对于我们做技术的人来说,光讲这些抽象的方法论还是不够的,如果具体到行为上,还是得落实到一些具体的技术上。

我的一个学弟,说起来还是非cs科班专业的,早早就意识到会一门硬技术的重要性。于是,他从大一时就开始混迹各大技术论坛,于是开始从Linux操作系统上手,从基础操作、到编写相关的脚本、再到阅读理解分析底层源码,对于Unix/Linux的理解很早就远超同龄人了。下面是他的故事

最初的故事还是从单片机开始的。

进入大学两个月,学院举办采访班导师的班级活动。班导师说大家学习完 C++ 课程后可以去学学单片机。我记在了心里。

大一第一个学期结束后的寒假,我凭借自己扎实的 C++ 基础学习了 C51 单片机,熟悉了使用 keil 软件的开发单片机的流程,了解了中断,定时,串口,I2C 总线,按键以及 LCD1602 液晶屏......由于了解到单片机也可以用汇编开发,所以顺道也学习了王爽老师编写的《汇编语言》, 甚至至今仍然记得书中的汇编程序do0

学习完后寒假还剩几天,我闲来无事,上网看看有没有什么和单片机相关的,可以继续学习。在随意翻看网页时我看到了树莓派,于是马上入手了。

从此,为了征服树莓派,我走上了 linux 学习的道路,期间不断学习 linux c 编程,python语言,bash脚本,mysql 数据库 ....... 期间做过很多好玩的开源项目,自娱自乐,也受到了一些已经工作了的技术人员的认同。

直到大二上,对编程的过度关注影响了我的学业。我也开始怀疑自己编程下去到底对不对,毕竟一开始我只是想做硬件而已。所以当时暂缓了编程的进一步学习,重新投入到自己的专业课中。大二下在学习数电,模电以及信号与系统的过程中,我接触到了verilog 硬件描述语言与 fpga,便开始了我的数字生涯。大三在不断学习物理以及数学的空闲时间里,我自学了数字信号处理以及 systemverilog 验证,同时也做过一些相关的项目。

尽管我的学业很顺利,自己凭借个人努力在大学中也学到了很多内容。

但我一直疑惑,我自己要的到底是什么,甚至经常熬夜思考。

直到有一晚上,我发现自己对探寻事物的本质有谜一般的兴趣。学习计算机语言,看 linux 内核的代码,学习数字逻辑以及物理学习都是我探寻事物本质的一些表现。所以我决定走上电子学和物理的道路。

当我亲眼看到粒子加速器的那一刻,我有种感觉——这就是我想要的

而且我觉得更有意思的一点就是,随着他几年的努力工作学习研究,他终于意识到自己的兴趣点其实在于研究更为底层的理论,目前已被某名校国家实验室录取,但是技术的成长不会背叛他,不仅在录取过程中让导师非常欣赏,并且在后续的研究工作中,也让他左右逢源,做什么事情都有自己的一套技术上的解决思路。即使不能直接用上,也能快速上手学习、应用一个技术达到一定程度,已经融入了他的血液中。

独立思考与批判性思维的缺失

其实,就技术谈技术,还是略显肤浅,毕竟授人以鱼不如授人以渔,实际上中国大学生普遍出现的通病就是做事被动:非要等老师、大佬说好才去做。此外,在社交方面,更是一个令人头疼的问题。很多擅长做技术的同学,对于搞人际关系,甚至是有点自傲的,觉得自己搞不好人际关系和是跟自己技术很强是互补一样。但是,作为一个优秀的人,成长应该是全面的,即使是为了做出更大的项目,寻找合适的人合作也是必不可少的,这点实在是很少有人能提及。

这是一种陋习,而这个陋习不得不说很大程度来自我们的教育体系甚至于文化:中学乃至于一定程度到了大学,管理者们总是以一种颇为军事的想法来思考如何管理学生,简单来说就是听话,一切要为了高考。是啊,考纲既然已经如此固定,在教学体系下保证最大程度的人能得到更多的分数,自然是很难容得下一些刺头的,因为他们或许有一些才能,但是会破坏正常的教学秩序。在这样的教育要求下,即使一些很叛逆的人,也渐渐磨平了棱角,至于本来就没什么想法的人,就更有些不敢越雷池一步了。

这种听话思维的延续,与一些诸如枪打出头鸟之类的文化结合,让人不要去乱想乱动。现在国家说我们需要专业人才,高考总体上却还是综合性的考试;我们需要创新性人才,可中学教育时往往又让学生不要违规,实在是有一点缘木求鱼的感觉。

美国著名认知学家侯世达在他的《表象与本质》中提出:类比与联想是人发展出新的思考的重要方法,伽利略发现木星的卫星,本质上不在于只有他第一次看见了卫星,而在于卫星这个词在英文中 (moon) 的原意特指地球的卫星月亮,如果不能突破这种当时会被教会认为是异端的思维的禁锢,这一伟大的科学发现就不可能出现。

所以,要习惯于创新,就要敢想敢做,有鲁迅先生所谓拿来主义的魄力,敢于对一切想法进行独立而科学的思考。

实际上,不仅是思考、做事,对于人际关系的发展,这点中国人大多要向美国人学习,当我第一天来到研究生学校时,我们第一天的课程居然是:教你如何使用领英,如何求职并进行 networking(我认为仅仅使用社交一词翻译似乎不太贴切,翻译为构筑自己的社会关系网似更准确)。正如面子之于中国人,social/networking 之于美国人也是一种家常便饭。他们教育人们要学会合理地结交有效的人脉,如果有志于相关方向的工作,就努力从亲戚、朋友、校友等等人处认识,完成一些原本只通过自己个人的想象不可能想到或者完成的事情。

这点国内也渐渐开始有了这样的氛围,尤其是在商业环境下。但是,还不够。甚至在文化上容易受到钻营之类的诟病。这点就又是一种对人与人性的合理发展的限制了。只要不违反规则与道德,人应该积极努力地为了改变自己的命运而努力,我觉得这是无可非议的。

对于还在高校内的学生也是,学校内往往也有科研训练、毕业设计或者纯粹地去加入一些知名教授的研究组从事一些科研或者工程工作的机会。如果能提前调查一下自己想研究的方向以及学校内教授们研究内容,大胆地写邮件或者用其他方式联系教授,也未必不能获得教授的指导和帮助,做出一些不错的工作来。或许在与教授的交往中,得到他的很多指导建议,从此又走上了一条更宽广的路,这种事情也并不鲜见。(小技巧,如果不清楚怎么找,也可以参考一下往年的优秀毕业设计名单等,往届能稳定带出成果的老师,往往较为擅长带学生)

当然,校外也有各种各样的项目、实习、工作等等机会,重要的还是走出自己的舒适区,给自己新的挑战,有时候多逼自己一把,也许事情就成了,人生也就打开了新的局面。

克服人性的劣根——人没有梦想,跟咸鱼有什么区别!

上一部分说了那么多制度上的问题,只是希望大家能跳出圈子来看问题,并不是为了给大家一个我xxxx都是社会的错的印象,实际上,真正的大佬,那都是些出淤泥而不染的人物,而要想成为一个技术大佬,也得努力克服自己身上存在的各种缺点,努力使自己成为一个更好的人,而不是因为找到了一些客观理由就当做救命稻草,为自己的无能作借口。

这个部分我们将着重于分析做好技术,个人的性格以及能力上重要的几个方面。

执行力的培养

执行力,在信息发达的当下,一定程度上已经变成了决定年轻人成绩的决定性因素了。因为很多人只是单纯去说xx技术好,我想学xx技术,然后学个两天,会打 hello world 就说自己学习过了,可是正如代码界名言 talk is cheap, show me the code 所表达的一样,水平没有真的提高,只是学会了几个术语,骗人可以,骗不了计算机的,总归真要用的时候是拿不出结果来的。

对于这一点,弥补的办法主要是坚持知行合一 的想法,就是永远要在实践中检查自己做的事情是不是真的配得上自己吹嘘的内容。王阳明在《传习录》中写道:人须在事上磨,方立得住,只有一次一次坚持把自己想做的事情落到实处,克服在这个过程中的厌恶、疲劳等等不利因素,才能在自控力上真正有点进步,能控制住自己的人,在有需要时,执行力自然高。

当然,这方面也有一些小技巧,比如说把困难的(准确的说是思维上最困难的,就是最厌恶最害怕做的麻烦事,不一定实际上做起来是最难的)事情最先做,这样,就像势如破竹一般,不会让自己陷入一直在简单工作的舒适区中徘徊的窘境,而复杂任务的进度也就能保证了。

当然,除了工作,适当地进行一些合理的享受,比如说欣赏音乐,或者参加各种体育活动,比起单纯地宅在家里,对身心意志健康的恢复,也自然会好很多。

这里需要重点提一句,体育锻炼除了增强体质、磨炼意志以外,我觉得对人的精神状态也有很积极的作用,对培养一种阳刚、向上的精神面貌也大有裨益。实际上,儒家的六艺里射(射箭)、御(骑马)都是体育运动;而在美国各大高校,健身房也几乎遍地都是,健身的文化非常浓郁。显然,经常参加体育锻炼不只是只有增强体质这一看似肤浅的效果的。(实际上,经常锻炼带来的体力进步,也有助于集中力的提高,很多人表示,看书可能一天最多只能集中六个小时以下,我个人在大学期间经常长跑,体质也好了很多,集中力也明显地上升到了后来能看到 8-10 小时)

执行力的提高,有一个心理误区是一定要克服的:同一个项目,他划水一点做,能得 85 分,我认真做,也就 88、90 分,有什么区别呢?无论什么样的进步,都是积少成多的,有时候分数虽然只是高一点,你对这个项目的理解的通透程度就是不一样的,或者你对这个技术能做到的一些边界条件、一些可能会在实际过程中出现的细节抓的很清楚,这样以后负责重大项目时,别人不行的东西,你行;别人做的不够完美的地方,你做的尽善尽美,这就是你的核心价值,一段升迁可能就源自于这么看似寻常的优秀:让这个人办事,稳重,放心,不需要太多的过问,省事,这不就是值得被拔擢的品质吗?

此外,正如史蒂芬·柯维先生在名著《高效能人士的七个习惯》所言,想提高自己的执行力,重要的是将自己的精力聚拢,多关心自己能改变的东西,把能量聚焦于可影响区,不要总是为了一些自己无力改变的事情去咸吃萝卜淡操心,一则是无用,只是乱想空发感慨,什么事也不会改变,一则是局外人、非专业人士对于相关的问题发表的见解,大多都是不准确甚至是完全错误的,如果总是把时间精力浪费在一些看似高尚实则跟自己无关的事情上,实际上反而影响了自己的不断进步以及本能期待的影响力、执行力的成长。

技术眼光的培养

这一点其实有点像做科研,对于一个技术领域,有条件的话,要搞清楚相关的技术的来龙去脉,有哪些技术路线,分为哪些派别,各个派别之间有什么优点和缺点,你更喜欢哪种,哪种更有可能继续发展下去?对于这些问题的回答,决定了你在相关的技术领域的深耕是否会在未来带来更大的价值。

当然,对于新入门或者还没有摸到门道的人来说,最快的方法还是想办法获取业界大佬的有针对性的观点。其实很多人都是,会花很多时间去寻找xx技术的 好老师 ,殊不知比起某个专门技术的老师更重要的,是寻找一个真正能在各方面教育你的导师,这样的人,不仅拥有对于技术的品味,更重要的是,他们往往对于做事、认识这个产业乃至于世界都是自己清晰完整的三观,这些方面的学习才是更潜移默化的成长,从这样的大佬学习经验、思考、工作的方法,从长远来看,才更有可能产生更加脱胎换骨的变化,而不仅仅是学到某一特定技术。

当然,做技术跟搞科研,甚至搞艺术是没有本质区别的,都是对未知、对真理的一种探索,永远都应该保持对于技术的热爱与敬畏。因为热爱,所以永远充满了学习的热情;因为敬畏,所以永远不敢以自己知道的小小一隅而自满自得,觉得自己不需要再学习了,殊不知技术的迭代与进步,实在是快到短短几年之间,就有可能发生整个观念上的改变的情况,如果不能 stay hungry, stay foolish ,则实在是很容易被淘汰掉。

就个人的进步而言,技术能力的培养可以来自两个方面,一则是在工作岗位上努力解决技术上的瓶颈、难题,另一则是在高校中通过接受正规的科研培训,提高自己分析、解决问题的能力。

先谈工作岗位,我看过一些技术大佬回忆自己的成长故事,除了极少数天才,大多也并不是一开始就天赋异禀或者说仅仅看看书、学学理论知识就超越常人的。但是他们回忆起自己成长的经历时,大多都是从回忆公司的项目开始的,比如说之前看一个腾讯大佬的回忆,就是进厂时被要求用纯 C 语言写一个能运行的并发服务器,其中不允许直接使用已经写好的框架,必须一点点自己查底层的内置标准库;后来是解决一个更大的服务器配置、优化的升级,于是他仔细阅读分析过去的源码,在看过去的大佬代码中学会了很多设计思想,然后实现了这个项目……在完成这些项目之前,其实他的水平也就跟一般人差不多,即使是完成之后,在某些细节知识体系等等上,他可能依旧是不如很多人的,但是这就是实践的魅力了,实践会逼着你删繁就简、直击要害,其实很多人还是有太多的学生思维,觉得很多事情不学个通透就不想、不敢着手去做,其实很多时候,有个大概理解,针对一个具体问题不断去调查、钻研、学习,才是更本质的、更迅速的能力,而且会逼着你把看似不相关的知识融会贯通,否则也就不能形成问题的体系了。

再讲讲正规的科研培训,实话说,中国除了少数名校或者知名研究组,大多在科研培训这块是很不如美国高校的。当然,这可能也与国内很多科研还是盲目跟风、关注的更多的是国际上 SCI 发表的情况有关。而在美国,很多论文的发表,需求往往出自一些公司,所以研究的结果经常能有直接的应用和衡量标准,从而较不容易陷入自嗨的境地,只是自己做一些理论结果。

而落实到个人,虽然在学校中做的内容,如果不是读博士、专业从事相关内容,大多数人实际上毕业后很难直接做到与自己的研究直接相关的工作。但是,这是不是就是说,认真研究就不重要呢?我觉得也不是,科研本质上是培养一种符合科学范式的工作、研究方法论。比如说,初到一个新的领域,如何调查、分析这个领域的发展情况,研究兴趣,是否有进一步研究的价值;对于一个问题,如何寻找不同的研究切入方法;对于一些方法,如何合理地进行批评与改进,等等等等。这些都不是一朝一夕就能学会的,都得在长期的工作中反复打磨,才能越来越提高自己的思考、研究能力。

其实,以代码为例,我认为计算机科学的进步很少有什么特别巨大的跃进,真正核心的理念其实也就那么多:比如说操作系统、算法等等,框架虽然会不断地在之前的基础上进行改进,但是创新不是一拍脑袋就能出现的,它总还得符合基本的原理,比如说谷歌的著名分布式框架 MapReduce,原理上还是用了一个分治法的思想。在学习的过程中,不满足于知识本身,更多地探究其为什么能想到、还能在其他地方怎么灵活运用,你也可以成为下一个化腐朽为神奇的创新者。

内驱力的培养

上面讲了很多热情相关的内容,这种热情其实就是所谓的内驱力,就是由人的思考、信念产生的驱使人不断克服厌倦、疲劳去克服一件件困难的精神。内驱力不足,归根结底是自己缺乏深厚的文化或者情感积淀而成的信念。

不过,人的意志力也的确是有限的,指望人能一直靠某个信念或者想法驱动自己,也实在是很难的事情,对于这个问题,主要的弥补办法是:读好书以及交益友,好的书籍往往都是各方面建树很大的大佬写出的充沛了各种信念与思考的产物,益友一般来说就是那些理想信念坚定、能力很强的人,人说起来是复杂的动物,但是有时也并不那么复杂,比如说人总归会下意识地模仿身边人的行为,因此如果朋友们都努力工作、积极向上,一个人也实在很难长时间保持很颓废的状态,这就是益友带来的好处了。

在这里,我想讲一个我个人认为很正确的信念,这也是无数中国古籍、无数仁人志士们都信奉的观点君子不器

所谓的,往往被解读为出人头地,因此家长们对孩子的期望都是成大器,本身自然没什么大问题。而且,如果不能成器,人往往在社会上不能得意,甚至连亲朋故旧的尊重往往都得不到。但是其实器也可以不这样理解,器的意思是不被一切世俗的规则与思想所奴役,获得不受约束的自由。(这与无数程序员羡慕的财务自由梦想在一定程度上是同义词)

自由永远是一把双刃剑,人不受各方面的约束,就容易堕落,容易为物所役,贪腐挥霍,整个人就堕落下去了。所以,虽然不器不是无器(不成器),但是如果沉迷于器,即使成了大器,之后也必然堕落为小人,心中永远要有对真理、对正义、对社会公正的那种良知,对还在遭受苦难的不幸的人们的怜悯,得到了要施于人,学到了要教给人,这样的人,才能得到人们真正的尊重,也才真的实现了人的自我实现。

其实,只有心中有这种不能割舍的热爱的人,才能不以物喜,不以己悲,在一次次失败与低谷中,不以个人的得失为意,坚持自己的信念,修身以俟,夭寿不二,达到那个理想的彼岸。

人的发展本质上是非线性的,这种让人无法预测的伟大进步,才是人类与人性真正伟大而令人感动的地方,也是教育真正可贵的地方。

即使是一个很无知很粗鄙的人(实际上,大家在很小的时候不都是这样的吗),如果寻到正道,努力修养锻炼自己数年,完全有可能变成一个有文化有思想有能力的充分发展的人才。

我觉得一切理想主义者的本质就是相信人类与人性的可能性,相信即使在无常的命运与世界中,人作为整体,总有一些个体,能与其不确定性抗争,并成长到可以面对命运、认清命运、抗击命运,而作为共同承认的一些信念,也就在这些人身上得到了传承,这也是教育最可贵的地方

虽然谁也不能说种下的种子一定会发芽成大树,但是谁又能说种下的种子就一定不可能发芽成参天大树呢?

人真的是一种一加一加一就能等于一百的动物,对自己、对身边的人都充满这样的信念与希望吧,人生的美好、人性的伟大,不也正在于此吗?

结语

本文字数虽长,但是都是长期思考的结果,不过鉴于很难一次性读完全部的要点,这里再做一次简要总结:

  1. 阅读书籍是与伟人们对话的过程,也是理解他们如何认识世界、改造世界的过程。从书籍中可以汲取他们的思想,领悟他们的品格,找到自己的定位,并指导自己的行为。
  2. 学会自省,构建自己的信仰价值体系,为自己真正需要坚守的价值燃烧自己的生命
  3. 机会总会到来,但是更重要的是在日常生活中关注细小的事情,不要老是做低快感阈值的事情,要多做一些具有长期价值的要事,克服行为成瘾
  4. 学会真正的深度工作的方法,提高自己工作的效率与产出
  5. *独立思考、独立思考、独立思考!再怎么强调它的重要性也不为过
  6. 走出舒适区,与更大的世界建立联系
  7. 人须在事上磨,方立得住
  8. 积极投身实践,在实践中抓住做事情的要害
  9. 君子不器,先成器再不器
  10. 相信人类与人性的可能性,发自真心关爱别人的成长

如果没有读出上述这些观点的,可以返回前面,再看一看,思考思考。

两年前,我偶然读到陈先发先生的《不做空心人》这篇演讲稿,文中的那种慷慨激昂的意气让我非常感动,读到之后,我甚至专门找了个本子,提笔把这篇文章工工整整抄了一遍。这里,我也想摘录几段我非常喜欢的句子(建议自行搜索全文):

越是在众声喧哗中,越需要一颗真正安静下来的心。越是快速变化的时代,越需要一颗真正慢下来的心。

世界上所有的美,都需要一种高度的专注和漫长时间的淬火。读书、求知,当然更不例外。有过乡村生活经验的人知道,长得过快的树,是空心的,其材质不堪大用。

在时代的屏幕上瞬间自生自灭的文字,越是浅陋粗鄙,就越需要有人能以坐得十年冷板凳的勇气,舍弃眼前之利、萤火之光,创造出能昭示一个时代良心和品质的精神产品,穿透这个时代流传下去。

越是有人觉得心里空荡荡的,就越需要另一群人懂得,应该往这种空荡荡中填补些什么

越是有人不再确信什么,觉得爱、理想、信仰成了过时的、陈旧而虚张的概念,就越是需要另一群人把这些词,高高地举在头顶。对内心贫乏的人来说,这些词是空的。而对内心丰富的人来说,它们永远是有血有肉的,是新鲜的。

要把求知、求学上的怀疑精神,与对生命价值的确认和信仰的形成融为一体。把对科学的冷眼观察、冷静探索,与做人做事的古道热肠融为一体。把探求真理的坚韧不拔,与生活中怜小怜弱、恤残恤孤的生命柔情融为一体。只有这样一颗有所爱的心,才不会空掉。我们才不致沦为一个空心人

世界上所有的美,都需要一种高度的专注和漫长时间的淬火。读书、求知,当然更不例外。有过乡村生活经验的人知道,长得过快的树,是空心的,其材质不堪大用。

在时代的屏幕上瞬间自生自灭的文字,越是浅陋粗鄙,就越需要有人能以“坐得十年冷板凳”的勇气,舍弃眼前之利、萤火之光,创造出能昭示一个时代良心和品质的精神产品,穿透这个时代流传下去。

越是有人觉得心里空荡荡的,就越需要另一群人懂得,应该往这种空荡荡中填补些什么

越是有人不再确信什么,觉得“爱”、“理想”、“信仰”成了过时的、陈旧而虚张的概念,就越是需要另一群人把这些词,高高地举在头顶。对内心贫乏的人来说,这些词是空的。而对内心丰富的人来说,它们永远是有血有肉的,是新鲜的。

要把求知、求学上的怀疑精神,与对生命价值的确认和信仰的形成融为一体。把对科学的冷眼观察、冷静探索,与做人做事的古道热肠融为一体。把探求真理的坚韧不拔,与生活中怜小怜弱、恤残恤孤的生命柔情融为一体。只有这样一颗有所爱的心,才不会空掉。我们才不致沦为一个空心人

谁的青春不迷茫呢?迷茫大概是无可避免的情况,但是就像鲁迅所说不满足是向上的车轮,重要的是不抛弃、不放弃,在逆境时的坚守,就像《周易》中所谓潜龙勿用,假以时日,也总有飞龙在天的一天。

借用一句毛主席的诗词风物长宜放眼量,事情总会慢慢起变化的,朋友们,勉之!

如有不同见解,欢迎戳下面二维码进行交流。

查看原文

邢爱明 回答了问题 · 2020-01-09

解决window没有安装ORACLE,可以用数据泵导出吗

expdp导出的文件就在服务器上,和本地是否安装oracle没什么关系,用客户端工具连接上数据库就能执行。

关注 2 回答 1

邢爱明 收藏了文章 · 2020-01-05

一张图彻底搞懂MySQL的 explain

explain关键字可以模拟MySQL优化器执行SQL语句,可以很好的分析SQL语句或表结构的性能瓶颈。

explain的用途

1. 表的读取顺序如何
2. 数据读取操作有哪些操作类型
3. 哪些索引可以使用
4. 哪些索引被实际使用
5. 表之间是如何引用
6. 每张表有多少行被优化器查询
......

explain的执行效果

mysql> explain select * from subject where id = 1 \G
******************************************************
           id: 1
  select_type: SIMPLE
        table: subject
   partitions: NULL
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
******************************************************

explain包含的字段

1. id //select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
2. select_type //查询类型
3. table //正在访问哪个表
4. partitions //匹配的分区
5. type //访问的类型
6. possible_keys //显示可能应用在这张表中的索引,一个或多个,但不一定实际使用到
7. key //实际使用到的索引,如果为NULL,则没有使用索引
8. key_len //表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
9. ref //显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值
10. rows //根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数
11. filtered //查询的表行占表的百分比
12. Extra //包含不适合在其它列中显示但十分重要的额外信息

图片版

一张图搞定explain

文字版

id字段

1. id相同
执行顺序从上至下
例子:
explain select subject.* from subject,student_score,teacher where subject.id = student_id and subject.teacher_id = teacher.id;
读取顺序:subject > teacher > student_score

一张图搞定 explain

2. id不同
如果是子查询,id的序号会递增,id的值越大优先级越高,越先被执行
例子:
explain select score.* from student_score as score where subject_id = (select id from subject where teacher_id = (select id from teacher where id = 2));
读取顺序:teacher > subject > student_score

一张图搞定 explain

3. id相同又不同
id如果相同,可以认为是一组,从上往下顺序执行
在所有组中,id值越大,优先级越高,越先执行
例子:
explain select subject.* from subject left join teacher on subject.teacher_id = teacher.id
 -> union 
 -> select subject.* from subject right join teacher on subject.teacher_id = teacher.id;
 读取顺序:2.teacher > 2.subject > 1.subject > 1.teacher

一张图搞定 explain

select_type字段

1. SIMPLE
简单查询,不包含子查询或Union查询
例子:
explain select subject.* from subject,student_score,teacher where subject.id = student_id and subject.teacher_id = teacher.id;

一张图搞定 explain

2. PRIMARY
查询中若包含任何复杂的子部分,最外层查询则被标记为主查询
例子:
explain select score.* from student_score as score where subject_id = (select id from subject where teacher_id = (select id from teacher where id = 2));

一张图搞定 explain

3. SUBQUERY
在select或where中包含子查询
例子:
explain select score.* from student_score as score where subject_id = (select id from subject where teacher_id = (select id from teacher where id = 2));

一张图搞定 explain

4. DERIVED
在FROM列表中包含的子查询被标记为DERIVED(衍生),MySQL
会递归执行这些子查询,把结果放在临时表中
备注:
MySQL5.7+ 进行优化了,增加了derived_merge(派生合并),默认开启,可加快查询效率
5. UNION
若第二个select出现在uion之后,则被标记为UNION
例子:
explain select subject.* from subject left join teacher on subject.teacher_id = teacher.id
 -> union 
 -> select subject.* from subject right join teacher on subject.teacher_id = teacher.id;

一张图搞定 explain

6. UNION RESULT
从UNION表获取结果的select
例子:
explain select subject.* from subject left join teacher on subject.teacher_id = teacher.id
 -> union 
 -> select subject.* from subject right join teacher on subject.teacher_id = teacher.id;

一张图搞定 explain

type字段

NULL>system>const>eq_ref>ref>fulltext>ref_or_null>index_merge>unique_subquery>index_subquery>range>index>ALL //最好到最差
备注:掌握以下10种常见的即可
NULL>system>const>eq_ref>ref>ref_or_null>index_merge>range>index>ALL
1. NULL
MySQL能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引
例子:
explain select min(id) from subject;

一张图搞定 explain

2. system
表只有一行记录(等于系统表),这是const类型的特列,平时不大会出现,可以忽略
3. const
表示通过索引一次就找到了,const用于比较primary key或uique索引,因为只匹配一行数据,所以很快,如主键置于where列表中,MySQL就能将该查询转换为一个常量
例子:
explain select * from teacher where teacher_no = 'T2010001';

一张图搞定 explain

4. eq_ref
唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
例子:
explain select subject.* from subject left join teacher on subject.teacher_id = teacher.id;

一张图搞定 explain

5. ref
非唯一性索引扫描,返回匹配某个单独值的所有行
本质上也是一种索引访问,返回所有匹配某个单独值的行
然而可能会找到多个符合条件的行,应该属于查找和扫描的混合体
例子:
explain select subject.* from subject,student_score,teacher where subject.id = student_id and subject.teacher_id = teacher.id;

一张图搞定 explain

6. ref_or_null
类似ref,但是可以搜索值为NULL的行
例子:
explain select * from teacher where name = 'wangsi' or name is null;

一张图搞定 explain

7. index_merge
表示使用了索引合并的优化方法
例子:
explain select * from teacher where id = 1 or teacher_no = 'T2010001' .

一张图搞定 explain

8. range
只检索给定范围的行,使用一个索引来选择行,key列显示使用了哪个索引
一般就是在你的where语句中出现between、<>、in等的查询。
例子:
explain select * from subject where id between 1 and 3;

一张图搞定 explain

9. index
Full index Scan,Index与All区别:index只遍历索引树,通常比All快
因为索引文件通常比数据文件小,也就是虽然all和index都是读全表,但index是从索引中读取的,而all是从硬盘读的。
例子:
explain select id from subject;

一张图搞定 explain

10. ALL
Full Table Scan,将遍历全表以找到匹配行
例子:
explain select * from subject;

一张图搞定 explain

table字段

数据来自哪张表

possible_keys字段

显示可能应用在这张表中的索引,一个或多个
查询涉及到的字段若存在索引,则该索引将被列出,但不一定被实际使用

key字段

 实际使用到的索引,如果为NULL,则没有使用索引
查询中若使用了覆盖索引(查询的列刚好是索引),则该索引仅出现在key列表

key_len字段

 表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度
在不损失精确度的情况下,长度越短越好
key_len显示的值为索引字段最大的可能长度,并非实际使用长度
即key_len是根据定义计算而得,不是通过表内检索出的

ref字段

 显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值

rows字段

 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需读取的行数

partitions字段

 匹配的分区

filtered字段

 查询的表行占表的百分比

Extra字段

 包含不适合在其它列中显示但十分重要的额外信息
1. Using filesort
说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取
MySQL中无法利用索引完成的排序操作称为“文件排序”
例子:
explain select * from subject order by name;

一张图搞定 explain

2. Using temporary
使用了临时表保存中间结果,MySQL在对结果排序时使用临时表,常见于排序order by 和分组查询group by
例子:
explain select subject.* from subject left join teacher on subject.teacher_id = teacher.id
 -> union 
 -> select subject.* from subject right join teacher on subject.teacher_id = teacher.id;

一张图搞定 explain

3. Using index
表示相应的select操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错!
如果同时出现using where,表明索引被用来执行索引键值的查找
如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
例子:
explain select subject.* from subject,student_score,teacher where subject.id = student_id and subject.teacher_id = teacher.id;
备注:
覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,即查询列要被所建的索引覆盖

一张图搞定 explain

4. Using where
使用了where条件
例子:
explain select subject.* from subject,student_score,teacher where subject.id = student_id and subject.teacher_id = teacher.id;

一张图搞定 explain

5. Using join buffer
使用了连接缓存
例子:
explain select student.*,teacher.*,subject.* from student,teacher,subject;

一张图搞定 explain

6. impossible where
where子句的值总是false,不能用来获取任何元组
例子:
explain select * from teacher where name = 'wangsi' and name = 'lisi';

一张图搞定 explain

7. distinct
一旦mysql找到了与行相联合匹配的行,就不再搜索了
例子:
explain select distinct teacher.name from teacher left join subject on teacher.id = subject.teacher_id;

一张图搞定 explain

8. Select tables optimized away
SELECT操作已经优化到不能再优化了(MySQL根本没有遍历表或索引就返回数据了)
例子:
explain select min(id) from subject;

一张图搞定 explain

使用的数据表

create table subject(
 -> id int(10) auto_increment,
 -> name varchar(20),
 -> teacher_id int(10),
 -> primary key (id),
 -> index idx_teacher_id (teacher_id));//学科表
 
create table teacher(
 -> id int(10) auto_increment,
 -> name varchar(20),
 -> teacher_no varchar(20),
 -> primary key (id),
 -> unique index unx_teacher_no (teacher_no(20)));//教师表
 
 create table student(
 -> id int(10) auto_increment,
 -> name varchar(20),
 -> student_no varchar(20),
 -> primary key (id),
 -> unique index unx_student_no (student_no(20)));//学生表
 
 create table student_score(
 -> id int(10) auto_increment,
 -> student_id int(10),
 -> subject_id int(10),
 -> score int(10),
 -> primary key (id),
 -> index idx_student_id (student_id),
 -> index idx_subject_id (subject_id));//学生成绩表
 
 alter table teacher add index idx_name(name(20));//教师表增加名字普通索引
 
 数据填充:
 insert into student(name,student_no) values ('zhangsan','20200001'),('lisi','20200002'),('yan','20200003'),('dede','20200004');
 
 insert into teacher(name,teacher_no) values('wangsi','T2010001'),('sunsi','T2010002'),('jiangsi','T2010003'),('zhousi','T2010004');
 
 insert into subject(name,teacher_id) values('math',1),('Chinese',2),('English',3),('history',4);
 
insert into student_score(student_id,subject_id,score) values(1,1,90),(1,2,60),(1,3,80),(1,4,100),(2,4,60),(2,3,50),(2,2,80),(2,1,90),(3,1,90),(3,4,100),(4,1,40),(4,2,80),(4,3,80),(4,5,100);
查看原文

邢爱明 回答了问题 · 2020-01-05

解决Mysql一个表有60多个字段算多吗?

1、单表60个字段不算多,比较复杂的业务对象,我见过有两三百个字段的。
2、创建表sql语句长,这个不是问题,本身是个一次性的事情,自己随手写个程序片段,或者在excel中处理一下,都是很简单的事情。

关注 4 回答 3

邢爱明 回答了问题 · 2020-01-05

mysql/PG等数据库中库、表、字段数、行数等应当控制在什么范围内,为什么?

表的数量和性能关系不大,应该关注单表的记录数,一般来说mysql的单表建议不超过一千万,oracle中有分区表,表记录数上亿也不是大问题。

当然这只是个参考值,具体要看表的访问方式、并发量和性能要求,最好自己模拟数据进行测试验证。

关注 3 回答 1

邢爱明 收藏了文章 · 2019-08-29

Array.from() 五个超好用的用途

翻译:刘小夕

原文链接:https://dmitripavlutin.com/ja...

因水平有限,文中部分翻译可能不够准确,如果你有更好的想法,欢迎在评论区指出。

更多文章可戳:https://github.com/YvetteLau/...

任何一种编程语言都具有超出基本用法的功能,它得益于成功的设计和试图去解决广泛问题。

JavaScript 中有一个这样的函数: Array.from:允许在 JavaScript 集合(如: 数组、类数组对象、或者是字符串、mapset 等可迭代对象) 上进行有用的转换。

在本文中,我将描述5个有用且有趣的 Array.from() 用例。

1. 介绍

在开始之前,我们先回想一下 Array.from() 的作用。语法:

Array.from(arrayLike[, mapFunction[, thisArg]])
  • arrayLike:必传参数,想要转换成数组的伪数组对象或可迭代对象。
  • mapFunction:可选参数,mapFunction(item,index){...} 是在集合中的每个项目上调用的函数。返回的值将插入到新集合中。
  • thisArg:可选参数,执行回调函数 mapFunction 时 this 对象。这个参数很少使用。

例如,让我们将类数组的每一项乘以2:

const someNumbers = { '0': 10, '1': 15, length: 2 };

Array.from(someNumbers, value => value * 2); // => [20, 30]

2.将类数组转换成数组

Array.from() 第一个用途:将类数组对象转换成数组。

通常,你会碰到的类数组对象有:函数中的 arguments 关键字,或者是一个 DOM 集合。

在下面的示例中,让我们对函数的参数求和:

function sumArguments() {
    return Array.from(arguments).reduce((sum, num) => sum + num);
}

sumArguments(1, 2, 3); // => 6

Array.from(arguments) 将类数组对象 arguments 转换成一个数组,然后使用数组的 reduce 方法求和。

此外,Array.from() 的第一个参数可以是任意一个可迭代对象,我们继续看一些例子:

Array.from('Hey');                   // => ['H', 'e', 'y']
Array.from(new Set(['one', 'two'])); // => ['one', 'two']

const map = new Map();
map.set('one', 1)
map.set('two', 2);
Array.from(map); // => [['one', 1], ['two', 2]]

3.克隆一个数组

JavaScript 中有很多克隆数组的方法。正如你所想,Array.from() 可以很容易的实现数组的浅拷贝。

const numbers = [3, 6, 9];
const numbersCopy = Array.from(numbers);

numbers === numbersCopy; // => false

Array.from(numbers) 创建了对 numbers 数组的浅拷贝,numbers === numbersCopy 的结果是 false,意味着虽然 numbersnumbersCopy 有着相同的项,但是它们是不同的数组对象。

是否可以使用 Array.from() 创建数组的克隆,包括所有嵌套的?挑战一下!

function recursiveClone(val) {
    return Array.isArray(val) ? Array.from(val, recursiveClone) : val;
}

const numbers = [[0, 1, 2], ['one', 'two', 'three']];
const numbersClone = recursiveClone(numbers);

numbersClone; // => [[0, 1, 2], ['one', 'two', 'three']]
numbers[0] === numbersClone[0] // => false

recursiveClone() 能够对数组的深拷贝,通过判断 数组的 item 是否是一个数组,如果是数组,就继续调用 recursiveClone() 来实现了对数组的深拷贝。

你能编写一个比使用 Array.from() 递归拷贝更简短的数组深拷贝吗?如果可以的话,请写在下面的评论区。

4. 使用值填充数组

如果你需要使用相同的值来初始化数组,那么 Array.from() 将是不错的选择。

我们来定义一个函数,创建一个填充相同默认值的数组:

const length = 3;
const init   = 0;
const result = Array.from({ length }, () => init);

result; // => [0, 0, 0]

result 是一个新的数组,它的长度为3,数组的每一项都是0。调用 Array.from() 方法,传入一个类数组对象 { length } 和 返回初始化值的 mapFunction 函数。

但是,有一个替代方法 array.fill() 可以实现同样的功能。

const length = 3;
const init   = 0;
const result = Array(length).fill(init);

fillArray2(0, 3); // => [0, 0, 0]

fill() 使用初始值正确填充数组。

4.1 使用对象填充数组

当初始化数组的每个项都应该是一个新对象时,Array.from() 是一个更好的解决方案:

const length = 3;
const resultA = Array.from({ length }, () => ({}));
const resultB = Array(length).fill({});

resultA; // => [{}, {}, {}]
resultB; // => [{}, {}, {}]

resultA[0] === resultA[1]; // => false
resultB[0] === resultB[1]; // => true

Array.from 返回的 resultA 使用不同空对象实例进行初始化。之所以发生这种情况是因为每次调用时,mapFunction,即此处的 () => ({}) 都会返回一个新的对象。

然后,fill() 方法创建的 resultB 使用相同的空对象实例进行初始化。不会跳过空项。

4.2 使用 array.map 怎么样?

是不是可以使用 array.map() 方法来实现?我们来试一下:

const length = 3;
const init   = 0;
const result = Array(length).map(() => init);

result; // => [undefined, undefined, undefined]

map() 方法似乎不正常,创建出来的数组不是预期的 [0, 0, 0],而是一个有3个空项的数组。

这是因为 Array(length) 创建了一个有3个空项的数组(也称为稀疏数组),但是 map() 方法会跳过空项。

5. 生成数字范围

你可以使用 Array.from() 生成值范围。例如,下面的 range 函数生成一个数组,从0开始到 end - 1

function range(end) {
    return Array.from({ length: end }, (_, index) => index);
}

range(4); // => [0, 1, 2, 3]

range() 函数中,Array.from() 提供了类似数组的 {length:end} ,以及一个简单地返回当前索引的 map 函数 。这样你就可以生成值范围。

6.数组去重

由于 Array.from() 的入参是可迭代对象,因而我们可以利用其与 Set 结合来实现快速从数组中删除重复项。

function unique(array) {
  return Array.from(new Set(array));
}

unique([1, 1, 2, 3, 3]); // => [1, 2, 3]

首先,new Set(array) 创建了一个包含数组的集合,Set 集合会删除重复项。

因为 Set 集合是可迭代的,所以可以使用 Array.from() 将其转换为一个新的数组。

这样,我们就实现了数组去重。

7.结论

Array.from() 方法接受类数组对象以及可迭代对象,它可以接受一个 map 函数,并且,这个 map 函数不会跳过值为 undefined 的数值项。这些特性给 Array.from() 提供了很多可能。

如上所述,你可以轻松的将类数组对象转换为数组,克隆一个数组,使用初始化填充数组,生成一个范围,实现数组去重。

实际上,Array.from() 是非常好的设计,灵活的配置,允许很多集合转换。

你知道 Array.from() 的其他有趣用例吗?可以写在评论区。

写在最后

翻译完又是凌晨一点,果然,没有一个成年人的生活是容易的。

谢谢各位小伙伴愿意花费宝贵的时间阅读本文,如果本文给了您一点帮助或者是启发,请不要吝啬你的赞和Star,你的肯定是我前进的最大动力。https://github.com/YvetteLau/...

推荐关注本人公众号:

clipboard.png

查看原文

邢爱明 回答了问题 · 2019-08-03

解决这段SQL是什么意思

这个是tnsname.ora文件的内容,oracle客户端连接服务器的配置文件,指定需要连接oralce服务器的IP地址、端口、数据库实例名称等关键信息。

关注 4 回答 3

邢爱明 回答了问题 · 2019-08-03

有一个数据库的表,每天会有上千条数据推送入库,该如何进行优化使得查询速度快?

如果数据表的行数在百万以下,单表查询确实不用太操心,注意建好合适的索引,查询的时候能用到就能满足大部分情况了。

如果数据持续增加,需要考虑数据归档或冷热数据分离的方案:
1、只保留最新6个月的数据,其他数据放到一个历史表里面
2、考虑人工分表的方案,每个月的数据放到一张单独的表,根据查询条件使用不同的表;oracle的话可以考虑用分区表,比人工分表更方便

关注 2 回答 2

邢爱明 收藏了文章 · 2019-06-30

简洁方便的集合处理——Java 8 stream流

背景

java 8已经发行好几年了,前段时间java 12也已经问世,但平时的工作中,很多项目的环境还停留在java1.7中。而且java8的很多新特性都是革命性的,比如各种集合的优化、lambda表达式等,所以我们还是要去了解java8的魅力。

今天我们来学习java8的Stream,并不需要理论基础,直接可以上手去用。

我接触stream的原因,是我要搞一个用户收入消费的数据分析。起初的统计筛选分组都是打算用sql语言直接从mysql里得到结果来展现的。但在操作中我们发现这样频繁地访问数据库,性能会受到很大的影响,分析速度会很慢。所以我们希望能通过访问一次数据库就拿到所有数据,然后放到内存中去进行数据分析统计过滤。

接着,我看了stream的API,发现这就是我想要的。

一、Stream理解

在java中我们称Stream为『』,我们经常会用流去对集合进行一些流水线的操作。stream就像工厂一样,只需要把集合、命令还有一些参数灌输到流水线中去,就可以加工成得出想要的结果。这样的流水线能大大简洁代码,减少操作。

二、Stream流程

原集合 —> 流  —> 各种操作(过滤、分组、统计) —> 终端操作

Stream流的操作流程一般都是这样的,先将集合转为流,然后经过各种操作,比如过滤、筛选、分组、计算。最后的终端操作,就是转化成我们想要的数据,这个数据的形式一般还是集合,有时也会按照需求输出count计数。下文会一一举例。

在这里插入图片描述

三、API功能举例

首先,定义一个用户对象,包含姓名、年龄、性别和籍贯四个成员变量:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Log4j
@Builder
public class User {
    //姓名
    private String name;
    //年龄
    private Integer age;
    //性别
    private Integer sex;
    //所在省市
    private String address;
}

这里用lombok简化了实体类的代码。

然后创建需要的集合数据,也就是源数据:

//1.构建我们的list
List<User> list= Arrays.asList(
        new User("钢铁侠",40,0,"华盛顿"),
        new User("蜘蛛侠",20,0,"华盛顿"),
        new User("赵丽颖",30,1,"湖北武汉市"),
        new User("詹姆斯",35,0,"洛杉矶"),
        new User("李世民",60,0,"山西省太原市"),
        new User("蔡徐坤",20,1,"陕西西安市"),
        new User("葫芦娃的爷爷",70,0,"山西省太原市")
);

3.1 过滤

1)创建流 stream() / parallelStream()

  • stream() : 串行流
  • parallelStream(): 并行流

2)filter 过滤(T-> boolean)

比如要过滤年龄在40岁以上的用户,就可以这样写:

List<User> filterList = list.stream().filter(user -> user.getAge() >= 40)
        .collect(toList());

filter里面,->箭头后面跟着的是一个boolean值,可以写任何的过滤条件,就相当于sql中where后面的东西,换句话说,能用sql实现的功能这里都可以实现

打印结果:

在这里插入图片描述

3)distinct 去重

和sql中的distinct关键字很相似。为了看到效果,此处在原集合中加入一个重复的人,就选择钢铁侠吧,复联4钢铁侠不幸遇害,大家还是比较伤心的。

List<User> list= Arrays.asList(
        new User("钢铁侠",40,0,"华盛顿"),
        new User("钢铁侠",40,0,"华盛顿"),
        new User("蜘蛛侠",20,0,"华盛顿"),
        new User("赵丽颖",30,1,"湖北武汉市"),
        new User("詹姆斯",35,0,"洛杉矶"),
        new User("李世民",60,0,"山西省太原市"),
        new User("蔡徐坤”,18,1,"陕西西安市"),
        new User("葫芦娃的爷爷",70,0,"山西省太原市")
);
//distinct 去重
List<User> distinctList = filterList.stream().distinct()
        .collect(toList());

打印结果:

在这里插入图片描述

4)sorted排序

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如:

Comparator.comparingInt

反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口。

//sorted()
List<User> sortedList = distinctList.stream().sorted(Comparator.comparingInt(User::getAge))
        .collect(toList());

打印结果:

在这里插入图片描述

结果按照年龄从小到大进行排序。

5)limit() 返回前n个元素

如果想知道这里面年龄最小的是谁,可作如下操作:

//limit 返回前n个元素
List<User> limitList = sortedList.stream().limit(1)
        .collect(toList());

在这里插入图片描述

6)skip()

与limit恰恰相反,skip的意思是跳过,也就是去除前n个元素。

打印结果:

在这里插入图片描述

果然,前两个人都被去除了,只剩下最老的葫芦娃爷爷。

3.2 映射

1)map(T->R)

map是将T类型的数据转为R类型的数据,比如我们想要设置一个新的list,存储用户所有的城市信息。

//map(T->R)
List<String> cityList = list.stream().map(User::getAddress).distinct().collect(toList());

打印结果:

在这里插入图片描述

2)flatMap(T -> Stream<R>)

将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流。

//flatMap(T -> Stream<R>)
List<String> flatList = new ArrayList<>();
flatList.add("唱,跳");
flatList.add("rape,篮球,music");
flatList = flatList.stream().map(s -> s.split(",")).flatMap(Arrays::stream).collect(toList());

打印结果:

在这里插入图片描述

这里原集合中的数据由逗号分割,使用split进行拆分后,得到的是Stream<String[]>,字符串数组组成的流,要使用flatMap的

Arrays::stream

将Stream<String[]>转为Stream<String>,然后把流相连接,组成了完整的唱、跳、rap、篮球和music。

3.3 查找

1)allMatch(T->boolean)

检测是否全部满足参数行为,假如这些用户是网吧上网的用户名单,那就需要检查是不是每个人都年满18周岁了。

boolean isAdult = list.stream().allMatch(user -> user.getAge() >= 18);

打印结果:

true

2)anyMatch(T->boolean)

检测是否有任意元素满足给定的条件,比如,想知道同学名单里是否有女生。

//anyMatch(T -> boolean) 是否有任意一个元素满足给定的条件
boolean isGirl = list.stream().anyMatch(user -> user.getSex() == 1);

打印结果:

true

说明集合中有女生存在。

3)noneMatch(T -> boolean)

流中是否有元素匹配给定的 T -> boolean 条件。

比如检测有没有来自巴黎的用户。

boolean isLSJ = list.stream().noneMatch(user -> user.getAddress().contains("巴黎"));

打印结果:

true

打印true说明没有巴黎的用户。

4)findFirst( ):找到第一个元素

Optional<User> fristUser  = list.stream().findFirst();

打印结果:

User(name=钢铁侠, age=40, sex=0, address=华盛顿)

5)findAny():找到任意一个元素

Optional<User> anyUser  = list.stream().findAny();

打印结果:

User(name=钢铁侠, age=40, sex=0, address=华盛顿)

这里我们发现findAny返回的也总是第一个元素,那么为什么还要进行区分呢?因为在并行流 parallelStream() 中找到的确实是任意一个元素。

Optional<User> anyParallelUser  = list.parallelStream().findAny();

打印结果 :

Optional[User(name=李世民, age=60, sex=0, address=山西省太原市)]

3.4 归纳计算

1)求用户的总人数

long count = list.stream().collect(Collectors.counting());

我们可以简写为:

long count = list.stream().count();

运行结果:

8

2)得到某一属性的最大最小值

// 求最大年龄
Optional<User> max = list.stream().collect(Collectors.maxBy(
Comparator.comparing(User::getAge)));

// 求最小年龄
Optional<User> min = list.stream().collect(Collectors.minBy(
Comparator.comparing(User::getAge)));

运行结果:

在这里插入图片描述

在这里插入图片描述

3)求年龄总和是多少

// 求年龄总和
int totalAge = list.stream().collect(Collectors.summingInt(User::getAge));

运行结果:

313

我们经常会用BigDecimal来记录金钱,假设想得到BigDecimal的总和:

// 获得列表对象金额, 使用reduce聚合函数,实现累加器
BigDecimal sum = myList.stream() .map(User::getMoney)
.reduce(BigDecimal.ZERO,BigDecimal::add);

4)求年龄平均值

//求年龄平均值
double avgAge = list.stream().collect(
Collectors.averagingInt(User::getAge));

运行结果:

39.125

5)一次性得到元素的个数、总和、最大值、最小值

IntSummaryStatistics statistics = list.stream().collect(
Collectors.summarizingInt(User::getAge));

运行结果:

在这里插入图片描述

6)字符串拼接

要将用户的姓名连成一个字符串并用逗号分割。

String names = list.stream().map(User::getName)
.collect(Collectors.joining(", "));

运行结果:

钢铁侠, 钢铁侠, 蜘蛛侠, 赵丽颖, 詹姆斯, 李世民, 蔡徐坤, 葫芦娃的爷爷

3.5 分组

在数据库操作中,我们经常通过GROUP BY关键字对查询到的数据进行分组,java8的流式处理也提供了分组的功能。使用Collectors.groupingBy来进行分组。

1)可以根据用户所在城市进行分组

Map<String, List<User>> cityMap = list.stream()
.collect(Collectors.groupingBy(User::getAddress));

在这里插入图片描述

结果是一个map,key为不重复的城市名,value为属于该城市的用户列表。已经实现了分组。

2)二级分组,先根据城市分组再根据性别分组

Map<String, Map<Integer, List<User>>> group = list.stream().collect(
        Collectors.groupingBy(User::getAddress, // 一级分组,按所在地区
                Collectors.groupingBy(User::getSex))); // 二级分组,按性别

运行结果:

在这里插入图片描述

3)如果仅仅想统计各城市的用户个数是多少,并不需要对应的list

按城市分组并统计人数:

Map<String, Long> cityCountMap = list.stream()
.collect(Collectors.groupingBy(User::getAddress,Collectors.counting()));

运行结果:

在这里插入图片描述

4)当然,也可以先进行过滤再分组并统计人数

Map<String,Long> map = list.stream().filter(user -> user.getAge() <= 30)
        .collect(Collectors.groupingBy(User::getAddress,Collectors.counting()));

运行结果:

在这里插入图片描述

5)partitioningBy 分区

分区与分组的区别在于,分区是按照 truefalse 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

//根据年龄是否小于等于30来分区
Map<Boolean, List<User>> part = list.stream()
        .collect(partitioningBy(user -> user.getAge() <= 30));

运行结果:

在这里插入图片描述

总结

到目前为止,stream的功能我们已经用了很多了,感觉有点眼花缭乱却无所不能,stream能做的事情远远不止这些。

我们可以多学习使用stream,把原来复杂的sql查询,一遍又一遍地for循环的复杂代码重构,让代码更简洁易懂,可读性强。

拓展阅读:Redis专题(1):构建知识图谱

Redis专题(2):Redis数据结构底层探秘

作者:杨亨

来源:宜信技术学院

查看原文

邢爱明 赞了文章 · 2019-06-30

简洁方便的集合处理——Java 8 stream流

背景

java 8已经发行好几年了,前段时间java 12也已经问世,但平时的工作中,很多项目的环境还停留在java1.7中。而且java8的很多新特性都是革命性的,比如各种集合的优化、lambda表达式等,所以我们还是要去了解java8的魅力。

今天我们来学习java8的Stream,并不需要理论基础,直接可以上手去用。

我接触stream的原因,是我要搞一个用户收入消费的数据分析。起初的统计筛选分组都是打算用sql语言直接从mysql里得到结果来展现的。但在操作中我们发现这样频繁地访问数据库,性能会受到很大的影响,分析速度会很慢。所以我们希望能通过访问一次数据库就拿到所有数据,然后放到内存中去进行数据分析统计过滤。

接着,我看了stream的API,发现这就是我想要的。

一、Stream理解

在java中我们称Stream为『』,我们经常会用流去对集合进行一些流水线的操作。stream就像工厂一样,只需要把集合、命令还有一些参数灌输到流水线中去,就可以加工成得出想要的结果。这样的流水线能大大简洁代码,减少操作。

二、Stream流程

原集合 —> 流  —> 各种操作(过滤、分组、统计) —> 终端操作

Stream流的操作流程一般都是这样的,先将集合转为流,然后经过各种操作,比如过滤、筛选、分组、计算。最后的终端操作,就是转化成我们想要的数据,这个数据的形式一般还是集合,有时也会按照需求输出count计数。下文会一一举例。

在这里插入图片描述

三、API功能举例

首先,定义一个用户对象,包含姓名、年龄、性别和籍贯四个成员变量:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Log4j
@Builder
public class User {
    //姓名
    private String name;
    //年龄
    private Integer age;
    //性别
    private Integer sex;
    //所在省市
    private String address;
}

这里用lombok简化了实体类的代码。

然后创建需要的集合数据,也就是源数据:

//1.构建我们的list
List<User> list= Arrays.asList(
        new User("钢铁侠",40,0,"华盛顿"),
        new User("蜘蛛侠",20,0,"华盛顿"),
        new User("赵丽颖",30,1,"湖北武汉市"),
        new User("詹姆斯",35,0,"洛杉矶"),
        new User("李世民",60,0,"山西省太原市"),
        new User("蔡徐坤",20,1,"陕西西安市"),
        new User("葫芦娃的爷爷",70,0,"山西省太原市")
);

3.1 过滤

1)创建流 stream() / parallelStream()

  • stream() : 串行流
  • parallelStream(): 并行流

2)filter 过滤(T-> boolean)

比如要过滤年龄在40岁以上的用户,就可以这样写:

List<User> filterList = list.stream().filter(user -> user.getAge() >= 40)
        .collect(toList());

filter里面,->箭头后面跟着的是一个boolean值,可以写任何的过滤条件,就相当于sql中where后面的东西,换句话说,能用sql实现的功能这里都可以实现

打印结果:

在这里插入图片描述

3)distinct 去重

和sql中的distinct关键字很相似。为了看到效果,此处在原集合中加入一个重复的人,就选择钢铁侠吧,复联4钢铁侠不幸遇害,大家还是比较伤心的。

List<User> list= Arrays.asList(
        new User("钢铁侠",40,0,"华盛顿"),
        new User("钢铁侠",40,0,"华盛顿"),
        new User("蜘蛛侠",20,0,"华盛顿"),
        new User("赵丽颖",30,1,"湖北武汉市"),
        new User("詹姆斯",35,0,"洛杉矶"),
        new User("李世民",60,0,"山西省太原市"),
        new User("蔡徐坤”,18,1,"陕西西安市"),
        new User("葫芦娃的爷爷",70,0,"山西省太原市")
);
//distinct 去重
List<User> distinctList = filterList.stream().distinct()
        .collect(toList());

打印结果:

在这里插入图片描述

4)sorted排序

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,如:

Comparator.comparingInt

反之, 需要调用 sorted((T, T) -> int) 实现 Comparator 接口。

//sorted()
List<User> sortedList = distinctList.stream().sorted(Comparator.comparingInt(User::getAge))
        .collect(toList());

打印结果:

在这里插入图片描述

结果按照年龄从小到大进行排序。

5)limit() 返回前n个元素

如果想知道这里面年龄最小的是谁,可作如下操作:

//limit 返回前n个元素
List<User> limitList = sortedList.stream().limit(1)
        .collect(toList());

在这里插入图片描述

6)skip()

与limit恰恰相反,skip的意思是跳过,也就是去除前n个元素。

打印结果:

在这里插入图片描述

果然,前两个人都被去除了,只剩下最老的葫芦娃爷爷。

3.2 映射

1)map(T->R)

map是将T类型的数据转为R类型的数据,比如我们想要设置一个新的list,存储用户所有的城市信息。

//map(T->R)
List<String> cityList = list.stream().map(User::getAddress).distinct().collect(toList());

打印结果:

在这里插入图片描述

2)flatMap(T -> Stream<R>)

将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流。

//flatMap(T -> Stream<R>)
List<String> flatList = new ArrayList<>();
flatList.add("唱,跳");
flatList.add("rape,篮球,music");
flatList = flatList.stream().map(s -> s.split(",")).flatMap(Arrays::stream).collect(toList());

打印结果:

在这里插入图片描述

这里原集合中的数据由逗号分割,使用split进行拆分后,得到的是Stream<String[]>,字符串数组组成的流,要使用flatMap的

Arrays::stream

将Stream<String[]>转为Stream<String>,然后把流相连接,组成了完整的唱、跳、rap、篮球和music。

3.3 查找

1)allMatch(T->boolean)

检测是否全部满足参数行为,假如这些用户是网吧上网的用户名单,那就需要检查是不是每个人都年满18周岁了。

boolean isAdult = list.stream().allMatch(user -> user.getAge() >= 18);

打印结果:

true

2)anyMatch(T->boolean)

检测是否有任意元素满足给定的条件,比如,想知道同学名单里是否有女生。

//anyMatch(T -> boolean) 是否有任意一个元素满足给定的条件
boolean isGirl = list.stream().anyMatch(user -> user.getSex() == 1);

打印结果:

true

说明集合中有女生存在。

3)noneMatch(T -> boolean)

流中是否有元素匹配给定的 T -> boolean 条件。

比如检测有没有来自巴黎的用户。

boolean isLSJ = list.stream().noneMatch(user -> user.getAddress().contains("巴黎"));

打印结果:

true

打印true说明没有巴黎的用户。

4)findFirst( ):找到第一个元素

Optional<User> fristUser  = list.stream().findFirst();

打印结果:

User(name=钢铁侠, age=40, sex=0, address=华盛顿)

5)findAny():找到任意一个元素

Optional<User> anyUser  = list.stream().findAny();

打印结果:

User(name=钢铁侠, age=40, sex=0, address=华盛顿)

这里我们发现findAny返回的也总是第一个元素,那么为什么还要进行区分呢?因为在并行流 parallelStream() 中找到的确实是任意一个元素。

Optional<User> anyParallelUser  = list.parallelStream().findAny();

打印结果 :

Optional[User(name=李世民, age=60, sex=0, address=山西省太原市)]

3.4 归纳计算

1)求用户的总人数

long count = list.stream().collect(Collectors.counting());

我们可以简写为:

long count = list.stream().count();

运行结果:

8

2)得到某一属性的最大最小值

// 求最大年龄
Optional<User> max = list.stream().collect(Collectors.maxBy(
Comparator.comparing(User::getAge)));

// 求最小年龄
Optional<User> min = list.stream().collect(Collectors.minBy(
Comparator.comparing(User::getAge)));

运行结果:

在这里插入图片描述

在这里插入图片描述

3)求年龄总和是多少

// 求年龄总和
int totalAge = list.stream().collect(Collectors.summingInt(User::getAge));

运行结果:

313

我们经常会用BigDecimal来记录金钱,假设想得到BigDecimal的总和:

// 获得列表对象金额, 使用reduce聚合函数,实现累加器
BigDecimal sum = myList.stream() .map(User::getMoney)
.reduce(BigDecimal.ZERO,BigDecimal::add);

4)求年龄平均值

//求年龄平均值
double avgAge = list.stream().collect(
Collectors.averagingInt(User::getAge));

运行结果:

39.125

5)一次性得到元素的个数、总和、最大值、最小值

IntSummaryStatistics statistics = list.stream().collect(
Collectors.summarizingInt(User::getAge));

运行结果:

在这里插入图片描述

6)字符串拼接

要将用户的姓名连成一个字符串并用逗号分割。

String names = list.stream().map(User::getName)
.collect(Collectors.joining(", "));

运行结果:

钢铁侠, 钢铁侠, 蜘蛛侠, 赵丽颖, 詹姆斯, 李世民, 蔡徐坤, 葫芦娃的爷爷

3.5 分组

在数据库操作中,我们经常通过GROUP BY关键字对查询到的数据进行分组,java8的流式处理也提供了分组的功能。使用Collectors.groupingBy来进行分组。

1)可以根据用户所在城市进行分组

Map<String, List<User>> cityMap = list.stream()
.collect(Collectors.groupingBy(User::getAddress));

在这里插入图片描述

结果是一个map,key为不重复的城市名,value为属于该城市的用户列表。已经实现了分组。

2)二级分组,先根据城市分组再根据性别分组

Map<String, Map<Integer, List<User>>> group = list.stream().collect(
        Collectors.groupingBy(User::getAddress, // 一级分组,按所在地区
                Collectors.groupingBy(User::getSex))); // 二级分组,按性别

运行结果:

在这里插入图片描述

3)如果仅仅想统计各城市的用户个数是多少,并不需要对应的list

按城市分组并统计人数:

Map<String, Long> cityCountMap = list.stream()
.collect(Collectors.groupingBy(User::getAddress,Collectors.counting()));

运行结果:

在这里插入图片描述

4)当然,也可以先进行过滤再分组并统计人数

Map<String,Long> map = list.stream().filter(user -> user.getAge() <= 30)
        .collect(Collectors.groupingBy(User::getAddress,Collectors.counting()));

运行结果:

在这里插入图片描述

5)partitioningBy 分区

分区与分组的区别在于,分区是按照 truefalse 来分的,因此partitioningBy 接受的参数的 lambda 也是 T -> boolean

//根据年龄是否小于等于30来分区
Map<Boolean, List<User>> part = list.stream()
        .collect(partitioningBy(user -> user.getAge() <= 30));

运行结果:

在这里插入图片描述

总结

到目前为止,stream的功能我们已经用了很多了,感觉有点眼花缭乱却无所不能,stream能做的事情远远不止这些。

我们可以多学习使用stream,把原来复杂的sql查询,一遍又一遍地for循环的复杂代码重构,让代码更简洁易懂,可读性强。

拓展阅读:Redis专题(1):构建知识图谱

Redis专题(2):Redis数据结构底层探秘

作者:杨亨

来源:宜信技术学院

查看原文

赞 120 收藏 86 评论 4

认证与成就

  • 获得 250 次点赞
  • 获得 47 枚徽章 获得 4 枚金徽章, 获得 18 枚银徽章, 获得 25 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2013-05-10
个人主页被 4.4k 人浏览