139
2018 这一年.md
Normal file
|
@ -0,0 +1,139 @@
|
|||
作者:CyC2018
|
||||
|
||||
链接:https://www.nowcoder.com/discuss/137593
|
||||
|
||||
来源:牛客网
|
||||
|
||||
## 前言
|
||||
|
||||
2018,有过迷茫,有过努力,也有很多收获。为了记录这一年以来的感受,于是有了这篇文章。
|
||||
|
||||
## Offer 情况
|
||||
|
||||
经过了长达一年左右的复习,秋招也收到了几个比较满意的 Offer,参加面试的都通过了。
|
||||
|
||||
- 百度,企业智能平台;
|
||||
- 阿里,高德地图,部门已联系,目前还在申报 Offer 中;
|
||||
- 腾讯,IEG 游戏平台,后台研发,SP;
|
||||
- 字节跳动,头条后台研发,SSP;
|
||||
- 华为,Cloud Bu;
|
||||
- 网易游戏,梦幻事业部;
|
||||
- 顺丰科技。
|
||||
|
||||
## 前期准备
|
||||
|
||||
也是在去年十一月份左右,看着身边两年制的同学经历了长时间而又艰难的秋招,我开始意识到自己应该提前准备了,否则自己的秋招会很惨。
|
||||
|
||||
本科的时候,虽然学过计算机网络、操作系统和数据结构等课程,而且 Leetcode 也刷了一两百题,但是离招聘要求还差的很远,学的都很浅只够应付考试,也没有实际的项目经验。
|
||||
|
||||
我的研究生方向是计算机图形学,研究生期间主要做一些科研项目。在选择招聘方向的时候,我也纠结了是不是找图形学相关方向的,但是考虑到图形学的选择不是很多,所以还是决定投后台研发相关的岗位。
|
||||
|
||||
于是开始收集各种学习资料,也买了很多纸质书。最开始的学习效率并不是很高,很迷茫,觉得要学的内容很多无从下手。那时候看别人的面经,感觉自己太弱了,很多内容都没接触过,于是更加迷茫。迷茫的时候总想着逃避,要是不复习多好,玩玩游戏每天多简单。但是游戏玩的越多,那种焦虑感越是强烈。解决焦虑的唯一办法就是想办法解决当前问题。当慢慢地从消极的学习态度中调整过来,掌握的知识越多,那种焦虑感也随之消失。当然这个过程并不容易,不仅需要很好的毅力,也要根据自身情况找到问题的有效解决方法。
|
||||
|
||||
## 春招开始
|
||||
|
||||
三月份各个公司就开始春招了,那时候刚把一些基础知识简单地复习了一下,Leetcode 刷到了三四百题。但是没有后台研发相关的项目,于是花了一个星期左右用 PHP 做了一个微博系统。当时做简历特别痛苦,没内容可以写,看着其他人简历各种新技术,自己都没掌握,所以很虚。
|
||||
|
||||
## 阿里一轮游
|
||||
|
||||
最开始投的阿里,实验室大几届有个师兄在天猫精灵团队,所以给我内推了。于是我人生中第一场面试就是阿里,很自然地被虐了一遍。记得当时约好下午两点电话面试,午饭都没吃,怕吃完之后犯困影响状态,然后找了一个很安静又没人的地方呆到了两点,调整自己的状态。可是面试官突然打电话来说有个会议要开,所以推迟了大概一个小时。苦苦等到三点左右,面试正式开始,不出所料面得非常糟糕。首先自己表述的很有问题,很多内容没回答到关键点上,自己会的内容也不怎么继续扩展回答。其次知识掌握得确实不够,连线程安全、ThreadLocal、函数式编程都不会。虽然被虐的很惨,但是也有好处,知道了面试到底是怎样的,自己还有哪方面的不足,该怎么准备。
|
||||
|
||||
## 腾讯被鞭尸
|
||||
|
||||
第二场面试是腾讯,在经历了阿里的面试之后,并且又继续复习了一段时间,我对面试就比较有信心了。一面其实回答的挺理想的,虽然很多问题没有立马回答出来,但是经过面试官的耐心提示之后都能回答一些内容。当时面了一个半小时,面试体验特别好。印象比较深刻的题目有,阅读一个 Redis 源码,分析存在哪些问题。其实就是一个计数器实现的限流算法,会有临界值的问题,但是当时没回答出来,只能听面试官给我解释。还有一个微信扫二维码,这个过程发生了什么,也没回答得很好,不过面试官也很耐心地纠正我回答上的错误。一面顺利通过了,但是总监面挂了。总监面没有问什么技术问题,就是问了问项目和职业规划。自己的项目确实比较 Low,我自己在介绍的时候也说得很不堪。职业规划我说自己希望在一些方面深入学习,因为自己现在在这些方面还很薄弱... 面完之后我就知道挂了,因为整个面试过程我都特别虚,还主动说自己技术能力不行。不出所料,面完的当天晚上,状态变成了不合适。
|
||||
|
||||
但是过了几天,突然收到腾讯的电话,问我是否愿意去深圳参加面试(笔者学校在广州)。当然我毫不犹豫地答应了,很开心腾讯还能给我机会。经过了上一场面试的启示,这次面试我表现地非常自信,自己知道的知识都很有信心地表达出来,被问到不会的内容也不会那么慌张,和面试官探讨一些细节,然后说说自己的想法,还有自己看过相关的内容。由于这是腾讯云部门,对 Linux 内核和 C++ 有很高的要求,问了几个相关的问题我都没回答出来,比如如何实现守护进程,Linux 信号机制,Linux 线程的不可中断阻塞状态如何进入等等。除了这些问题,其它地回答的都还行。遗憾的是,当天晚上面试官打电话告知我面试没通过。但是他说我其它方面都很不错,所以问我愿不愿意参加腾讯云 Java 部门的招聘,于是第二天我又去了一个新的部门面试。
|
||||
|
||||
这次面试是在部门的会议室进行的,进到公司之后说实话没有自己想象中那么好,工位很挤环境一般。一开始就先随便聊聊,学校的研究工作,学习之类的。然后看了看项目,看完之后我就知道凉了一半,这个项目确实太水了,面试官看了之后没有接着问,也能感受到面试官有点嫌弃。然后他就问了一些基础知识,问到进程调度算法,面试官让我实现一个任务调度系统。因为是第一次手写代码,而且之前确实没考虑过这个问题,然后就胡乱写了一堆代码,特别乱,而且到处涂改。显然面试官是不满意的,写了也有十几分钟之后,我自己都知道已经凉了,然后面试官没让我接着写,也没给我任何提示,说就到这里,面试结束了,还有没有什么问题想问的。当然看过任务调度系统相关的文章会觉得挺容易的,比如使用时间轮实现等等。我依然记得面试官送我出门时候的热情,送我坐电梯的时候还很热情地和我说,非常感谢参加本次面试,辛苦了。
|
||||
|
||||
## 虎牙过于自信
|
||||
|
||||
经过了阿里和腾讯的面试之后,我觉得自己大概已经知道该怎么面试了,面试时候该注意什么,该怎么表达等等。而且腾讯面试表现也不差,虽然最后没通过。所以在虎牙面试的时候特别放松,觉得应该能通过。前面面的也都还行,虽然有几个问题没回答好,比如分析一下微博的时间线。通过了第一轮面试直接等第二轮,等到了晚上七点多才等到我。虎牙面试还是很注重技术的,虽然问的都不是很深入,只要简单回答到点上就不会接着问下去。二面也有一些问题没回答好,比如 ConcurrentHashMap 的并发机制,问 Spring 直接说不会。也有一些问题回答得比较乱,没有条理。但是我觉得大部分问题都回答的不错,应该能通过。可是面试完之后,面试官问有没有什么问题要问他,由于太过放松,我就问你们都加班到这么晚不吃饭吗,好饿啊,周六周日还加班吗... 问完之后面试官就很严肃了,说平常不加班的,我突然意识到了问题的严重性... 最后还是凉了。
|
||||
|
||||
## 百度第一个 Offer
|
||||
|
||||
被三家连续拒了之后,都开始怀疑自己了,不过还是提醒自己要保持信心。
|
||||
|
||||
幸运的是,百度的面试非常适合我,三轮都是技术面,而且手写算法题目居多,而我准备最多的是算法,所以很顺利通过了面试。但是面试表现并没有特别好,过了比较长的时间才被捞,而且是工程效率部门,做内部工具的,对个人成长并不好,所以不是特别满意。
|
||||
|
||||
## 网易游戏最好的面试体验
|
||||
|
||||
其实最开始没有打算投网易游戏的,因为被脉脉洗脑,已经放弃了做游戏。但是因为前面面试基本被拒了,担心没有实习 Offer,因此就试试看。
|
||||
|
||||
因为没有特别想去网易游戏,所以面试过程也比较放松,就当去聊聊天。面试官非常 nice,那天下午挤了很久地铁,比较口渴,然后面试官看我说得沙哑了,到门口帮我买了一瓶可乐,非常感激。面试之前我就提出我对 C++ 不熟悉,最近主要看 Java 的内容。面试官还是说没关系,尽量回答就好。当然最后我都把问题往 Java 那里回答了,比如 Map 的实现,内存管理等等。最后聊了一些玩过的游戏,就让我回去等消息。网易游戏就一轮面试,确实就一轮。周五参加的面试,下周一就给 Offer 了,效率特别高。
|
||||
|
||||
## 微众玄学面试
|
||||
|
||||
通过微众面试我自己都非常吃惊,一面的时候就简单自我介绍了一下,然后面试官开始介绍他自己的工作经历,以及现在部门在做的内容。之后问了我一个场景分析问题,我想了一会儿没想出来,于是面试官拿起草稿纸把各种需求详细说了一遍,然后把系统架构图也画了出来... 最后他问还有什么我优势的地方他没问到的,我问他怎么不问问算法题,他说笔试都通过了没必要再问。面完之后我觉得聊得很开心,但是技术问题没回答好,出乎意料收到了二面通知。二面没问技术,就让介绍了项目,再问问家住哪之类的问题,也顺利通过了。HR 面就不用介绍。收到了微众的 Offer,得知了部门是贷款科技部,非常核心,很吃香,近几年也在扩展一些业务,还是有点小心动的。虽然最后没选择去微众实习,但是一面面试官加了我微信,我很感谢他一面非常耐心给我讲解,并让我通过。他说我是他面试的第一顺位,也就是第一个面试者,所以会放宽很多,也希望我秋招能加入他们。
|
||||
|
||||
## 实习选择
|
||||
|
||||
其实最理想的是去百度实习,秋招也会容易很多。但是考虑到百度是在北京,部门很边缘,而且需要实习很长时间也不一定能转正,所以还是放弃了。最后只能在网易游戏和微众选,虽然自己不想做游戏,但是考虑到网易游戏的平台认可程度比微众好,秋招肯定会更容易一些。而且秋招如果还想进微众的话也会比较容易,因为面试官和 HR 都说秋招的时候会优先考虑我,所以最后还是去了网易游戏实习。
|
||||
|
||||
## 实习之前的快速学习期
|
||||
|
||||
经历了春招之后,认识到了自己身上的不足,比如交流表达能力的欠缺,知识积累得不够,项目深度不够。因此在实习之前的两三个月,开始针对这些问题逐个解决。
|
||||
|
||||
- 交流表达能力欠缺,就提前准备好各种非技术问题,然后对着镜子回答,把自己当成听众,并且也用录音机录下来。
|
||||
- 知识积累不够,采取的策略是保证广度优先,并且在重要的内容上保证深度。其实之前基础知识已经掌握的比较好了,再学其它技术的时候都有很多相同的地方,所以学起来很快。
|
||||
- 项目深度不够,就把那个微博系统做了一点改进,学了 Spring 之后改用 Java 实现。
|
||||
|
||||
## 不那么安心的实习
|
||||
|
||||
去实习的时候还是挺惊喜的,因为我被安排的工作是游戏引擎相关的,和自己的研究生方向紧密相关,我觉得做完实习项目之后自己的毕业论文也会比较有灵感。
|
||||
|
||||
但不幸的是,在去的第一天部门接待聚餐上,服务端主程就说,我们部门工作制是九九六,现在互联网都是九九六。在实习之前我了解的是实习生六点就可以走,而且只用上五天班,听到他这么一说心都凉透了,因为已经想好了晚上和周末时间用来复习。如果知道是九九六,我会选择去百度。
|
||||
|
||||
其实网易游戏部门氛围还是不错的,对员工很好,而且我的实习导师人也很好,在我生病的那几天很关心我。但是九九六的工作制对秋招复习还是有很大影响的,而且每天上下班花在路上的时间超过了两个小时,下班回寝室之后总想着看会儿视频休息一下,然后又要早早睡觉赶着第二天上班。没办法只能在上下班地铁上复习,还有就是午休时间接着复习。
|
||||
|
||||
## 秋招开始
|
||||
|
||||
实习之后已经是九月份了,那时候已经错过了所有提前批。而且实习的时候没怎么复习,九月初还是感觉没怎么准备充分,所以就又等了半个月才开始投简历。
|
||||
|
||||
但是这个时候和春招相比,已经把大部分后台研发相关的知识点过了一遍,很多重要的内容前前后后也看了十几遍,没有春招时候那么迷茫和焦虑。即使被问到没有掌握的知识,我也有把握通过讨论的方式,给出大概的思路,因为很多技术确实是相通的。
|
||||
|
||||
## 阿里看不懂的内部流程
|
||||
|
||||
秋招第一个投递的依然是阿里,最开始系统自动发起了一个新的流程,然后过了几天自动回绝了... 八月末的时候也找人内推了,但是又被阿里直接回绝了... 那时候已经觉得可能是春招面试表现太差,此生无缘阿里了。可是过了一段时间,正式校招的时候,阿里又发起了一个新的流程戏弄我,收到笔试通知的时候,我还犹豫了到底参不参加,因为那时候已经九月中旬,听说阿里已经没有 HC 了。而且按前面回绝我的态度,感觉即使笔试通过面试也通过不了。笔试那天晚上,本来准备看个电影放松一下,后来想了想还是参加了笔试,笔试各种机器学习和数学题,感觉拿错了试卷,笔试完我已经把阿里从我的公司进度列表中删除了,不再纠结阿里。可是过了一段时间收到阿里的面试通知,我以为是走走形式,可能参加笔试的人很少了,所以才选中我参加面试。那时候阿里招聘官网状态一排的已回绝,让我对阿里有一种恐惧感,觉得面试肯定挂。但是真正面试的时候却意外的顺利,收到二面通知的时候特别激动,然后面完二面又让直接等 HR 面,HR 面虽然不是很理想,但是没有很大的问题。又过了很长一段时间,在我去深圳参加腾讯招聘的高铁上,收到了高德地图 HR 的电话,问是否愿意去。虽然得知部门在北京有点小失落,但是还是很开心终于被阿里认可了,摆脱了对阿里的恐惧。
|
||||
|
||||
实验室上届毕业在阿里云的大佬某天突然和我说,他们部门有新的 HC,让我把简历发给他,他要帮我内推,会帮我安排一场线下面试,如果通过的话,到时候和高德的 HR 沟通一下,直接把我从高德捞过来。很感谢大佬向他老大极力推荐我,给我了这次面试机会。线下面试也很顺利,聊聊实习项目,问问我的开源博客,然后问些 Paxos 等分布式的问题,还有就是手写代码,信号量实现生产者消费者,以及一个位运算的问题。其实位运算的问题面试的时候写的不完善,面试官让我之后完善了再发给他,因为面试一个多小时有点长了。过后我写了详细文档讲解了思路,以及使用 JUnit 进行了详细的单元测试,把文档和代码都发给了他。现在面试已经通过了,但是最近阿里集团 HC 比较紧张,也不知道能不能批到 HC。
|
||||
|
||||
## 百度又是不那么满意的部门
|
||||
|
||||
虽然阿里是最先开始流程,但是第一个参加面试的是百度。因为实习的时候通过了百度的面试,所以这次面试还是比较有信心的。百度面试连续三天,都在同一个地方,最后签约也在同一个地方。还记得每次都坐一个小时左右的地铁去那里,路线已经非常熟悉了,和每天去实习的感觉类似。百度面试比较注重技术,三轮面试基本都是问技术问题,而且问的也比较深入,内容也非常广。但是面的不是那么理想,有两点原因,首先是因为确实有些知识点还没掌握好,比如 AC 自动机,系统故障分析等等;其次是对实习项目的描述上还不够好,没有把实习内容的闪光点描述出来,也没有讲清楚为什么做这个项目,自己通过什么方法去做,以及最后的结果。
|
||||
|
||||
最后百度给了白菜价,部门是企业智能平台,主要是内部系统,虽然会接触到机器学习和大数据。
|
||||
|
||||
## 腾讯虐我千百遍
|
||||
|
||||
秋招腾讯第一场面试和实习参加腾讯面试的感觉非常像,第一轮技术面感觉很好,手写堆排序算法,二部图分析等等。面完之后通知待会儿二面,听到之后还是很激动的,觉得这次应该没问题了。我在等二面的时候,碰到了室友(他经常不住宿舍,所以不清楚他也去面试),聊着聊着居然发现我两是同一个面试官,而且他是来二面的,也就是等一下我两就要一前一后进去面试。二面的感觉和实习二面非常像,非技术问题回答的支支吾吾,然后面试官开始质疑我说的内容,给我压力,我没有当场反驳,就说了哦,好像是这样的。因为面试官全程都绷着脸,所以我也比较紧张,很多问题没回答好。过了几天,室友和我说收到 HR 面试通知了,我去官网看了一下状态,已经变成了熟悉的不合适。这次面试失败的主要原因是自己在应对这种压力时处理地不是很好,主要体现在失去信心以及紧张。解决方法也简单,做好充分准备来保持信心,受到质疑的时候积极反驳,紧张的时候计时调整心态,可以试试深呼吸或者喝水。
|
||||
|
||||
因为实习有被捞起来的经历,所以被拒之后我特别希望能继续被捞起来,然后把简历上的面试城市改成了深圳。苦苦等到深圳场面试的前几天,在不经意的一个下午手机突然响了,我记得是短信邮件同时收到面试通知。于是又开始了新一轮被腾讯虐的面试之旅。
|
||||
|
||||
一面和之前一样也是意外地顺利,虽然问了一些 C++ 的问题,但是我都说到 Java 相关的实现上。在一些问题上确实回答的深度不够,比如网络编程里面的水平触发和边缘触发等问题。然后问了几个算法,本来要求手写,我说我实现过,所以就讲了讲思路。面试和腾讯第一场面试一样持续了一个半小时,面试官也很好,很多问题都会给提示,即使最开始回答的有问题。二面面试官也很好,问了问实习项目,然后再聊一聊一些技术,经过了之前的面试,到这次面试真的就像在聊天一样而不是面试,我们都会说一些对技术上的理解。HR 面其实面得很差,对于非技术问题的吹水能力我还是不太行。最终和我预期的一样,给了我 SP 的 Offer,因为觉得自己面得还可以,但是也不够好到给 SSP,有些 C++ 问题还是没回答的特别好。
|
||||
|
||||
## 头条意外的惊喜
|
||||
|
||||
之前看到学弟收到头条的 Offer,薪资非常诱人,所以也想去试试。也听说头条面试难度非常大,主要考察手写算法,因为自己算法方面准备得比较充分,所以觉得会比较顺利,但是也没有特别高的预期。前两面中规中矩,算法题和其它问题我都回答的比较好,到三面的时候,问了一个错排问题,其实最开始我给了正确的递推公式,但不是面试官想要的答案,所以让我再想想。我想了十几分钟还是觉得没问题,那时候觉得自己已经凉了,因为面试官一直不满意。后面的几个问题也没回答的很好,分析一个 SQL 语句的具体执行过程,比如会怎么利用索引,怎么优化之类的,虽然在他的提示下还是回答了,但是感觉并不好。面完之后我立马查了一下那个错排问题,证实了我的答案是正确的,于是写了一个详细的文档,联系 HR 让她发给面试官。出乎意料的是,HR 让我不用担心,他说面试官对我的评价很好... 不过最后还是让她把文档发给了面试官。之后收到了加面通知,头条加面有两种情况,一是三轮评级都是 4 可以评 SSP,二是面试官评价差别很大,再面一轮决定是否录用。收到加面的时候完全不知道自己属于哪一种,感觉两种情况都有可能。加面回答的也不好,主要是问项目,面了 25 分钟就草草结束,最后面试官说有些内容需要找一些文献参考参考。面完之后我觉得,即使我属于第一种要评 SSP 的情况,加面面的那么差应该也没希望了。苦苦等了好多天之后,最后确定是 SSP 之后,还是很惊喜的,感觉是对自己这么长时间复习的一个认可。
|
||||
|
||||
## 顺丰最后的保底
|
||||
|
||||
投顺丰是因为九月中旬很多公司都结束了招聘,所以那时候比较慌,就投了顺丰当做保底,顺便也练练手。最开始还担心顺丰笔试没通过,因为编程题最后一题没做出来,那题的题目都出错了,而且题目是网上直接 copy 过来的,网上的源码都不能通过,更别说我自己的实现了。顺丰面试主要问了数据库的内容,而且问的特别深,几乎把每种日志的实现和作用都问了一遍。面顺丰的时候也比较早,那时候有些问题的回答上没有组织好,回答得比较凌乱,虽然最后也算给了一个小 SP。
|
||||
|
||||
## 华为特别纠结的部门
|
||||
|
||||
去华为面试确实是没有压力的,因为都知道华为面试不怎么问技术,虽然还是问了我一些技术问题,不过不是问的很深。面试主要介绍项目,我对自己的实习项目还是比较有信心的,因为觉得做的确实不错,而且面了很多场了,知道该怎么介绍项目。面试官问我个人意愿,我说自己对分布式中间件等比较感兴趣,于是面试官把我推荐到了 Cloud Bu。本来没打算签华为的,现场签约也就去看看到底给我开多少。最开始其实给我开了十四级最高的薪资,我本来不是很想去,虽然对这个部门感兴趣,但是薪资确实比不上头条。然后随口问了一句可不可以给十五级,本来 HR 说是可以试着申请一下,不过最后没申请成功。
|
||||
|
||||
## 技术博客
|
||||
|
||||
最后安利一下自己的技术博客:[CS-Notes](https://github.com/CyC2018/CS-Notes),虽然现在还有很多不完善的地方,但以后会不断改进。
|
||||
|
||||
## 小结
|
||||
|
||||
很多人都说,面试和考试一样,要背很多没用的东西。最开始我也认同这种看法,可是参加了几场面试之后,我就不这么认为了。因为面试出的问题,有很多是实际开发中碰到的,所以准备面试相当于提前做入职准备。而且面试中考察的思维能力、交流表达能力、应对压力能力,都是真正工作中所需要的。
|
||||
|
||||
我觉得自己比别人做的好的地方是,有很强烈的想找到好工作的意愿,才驱使我不断学习,所以态度很重要。
|
||||
|
||||
信心源自于充分准备,有了信心,面试的时候才能游刃有余。而毫无依据的自我感觉良好,在每次失败之后都看不到自身的不足,而是怪罪于外界因素。
|
||||
|
||||
做好自己的简历,我在简历上花了很长时间,只要允许,我都会用这个简历给面试官演示:[个人简历](https://cyc2018.github.io)。
|
46
Additional.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
# 前言
|
||||
|
||||
本文档提供博客内容的补充资料,记录一些还没写又很重要的内容。欢迎补充!
|
||||
|
||||
格式:\[资料名称\]\[标签1\]\[标签2\]...
|
||||
|
||||
|
||||
# 系统设计
|
||||
|
||||
- [[system-design-primer][Github]](https://github.com/donnemartin/system-design-primer)
|
||||
- [[Leetcode / Interview Questions][题集]](https://leetcode.com/discuss/interview-question/?orderBy=most_votes)
|
||||
- [[系统设计面试题精选][Gitbook]](https://soulmachine.gitbooks.io/system-design/content/cn/)
|
||||
- [[海量数据面试题]](https://samanthachen.github.io/2016/08/01/%E6%B5%B7%E9%87%8F%E6%95%B0%E6%8D%AE%E9%9D%A2%E8%AF%95%E9%A2%98/)
|
||||
- [[前端经典面试题: 从输入 URL 到页面加载发生了什么?][具体问题]](https://segmentfault.com/a/1190000006879700)
|
||||
- [[秒杀系统架构分析与实战][具体问题]](https://my.oschina.net/xianggao/blog/524943)
|
||||
- [[微信二维码登录原理][具体问题]](https://zhuanlan.zhihu.com/p/22325152?refer=bittiger)
|
||||
- [[Create a TinyURL System][具体问题]](https://github.com/CyC2018/CS-Notes)
|
||||
- [[Design a Key-Value Store (Part I)][具体问题]](http://blog.gainlo.co/index.php/2016/06/14/design-a-key-value-store-part-i/)
|
||||
- [[坦率地讲 服务熔断 & 服务降级][知识点]](http://lexuslee.me/2018/02/01/2018-01-18-Service-fallback/)
|
||||
- [[理解 HTTP 幂等性][知识点]](https://www.cnblogs.com/weidagang2046/archive/2011/06/04/idempotence.html)
|
||||
- [[接口限流算法][知识点]](https://blog.csdn.net/ljj821061514/article/details/52512943)
|
||||
- [[微服务学习资料汇总][知识点]](https://www.infoq.cn/article/2014%2F07%2Fmicroservice-learning-resources)
|
||||
- [[理解 RESTful 架构][知识点]](http://www.ruanyifeng.com/blog/2011/09/restful.html)
|
||||
- [[MapReduce 算法][知识点]](https://github.com/xuelangZF/CS_Offer/blob/master/Others/Hadoop_Spark.md)
|
||||
|
||||
# Spring
|
||||
|
||||
- [[Spring 揭秘][书籍]](https://book.douban.com/subject/3897837/)
|
||||
- [[69 道 Spring 面试题和答案][面试题集锦]](http://ifeve.com/spring-interview-questions-and-answers/)
|
||||
- [[Spring 面试题][面试题集锦]](https://github.com/Homiss/Java-interview-questions/blob/master/%E6%A1%86%E6%9E%B6/Spring%20%E9%9D%A2%E8%AF%95%E9%A2%98.md)
|
||||
|
||||
# 中间件
|
||||
|
||||
- [[RabbitMQ 实战][书籍]](https://book.douban.com/subject/26649178/)
|
||||
- [[从 Paxos 到 Zookeeper][书籍]](https://book.douban.com/subject/26292004/)]
|
||||
- [[Apache Dubbo][文档]](http://dubbo.apache.org/zh-cn/)
|
||||
- [[nginx 快速入门之基本原理篇][入门]](https://zhuanlan.zhihu.com/p/31196264)
|
||||
- [[深入理解 Nginx][书籍]](https://book.douban.com/subject/22793675/)
|
||||
|
||||
# 编程语言思想
|
||||
|
||||
- [[函数式编程初探][入门]](http://www.ruanyifeng.com/blog/2012/04/functional_programming.html)]
|
||||
- [[函数式编程][全面]](https://coolshell.cn/articles/10822.html)
|
||||
- [[闭包][文档]](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures)
|
||||
- [[尾调用优化]](http://www.ruanyifeng.com/blog/2015/04/tail-call.html)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# 数据结构与算法
|
||||
|
||||
# 数据结构与算法
|
||||
|
||||
- [算法](https://book.douban.com/subject/19952400/)
|
||||
- [数据结构与算法分析](https://book.douban.com/subject/3351237/)
|
||||
- [编程珠玑](https://book.douban.com/subject/3227098/)
|
||||
|
@ -41,6 +41,7 @@
|
|||
|
||||
- [Java 编程思想](https://book.douban.com/subject/2130190/)
|
||||
- [Effective java 中文版](https://book.douban.com/subject/3360807/)
|
||||
- [深入Java虚拟机(原书第2版)](https://book.douban.com/subject/1138768/)
|
||||
- [深入理解 Java 虚拟机](https://book.douban.com/subject/24722612/)
|
||||
- [Java 并发编程实战](https://book.douban.com/subject/10484692/)
|
||||
- [精通 Spring 4.x](https://book.douban.com/subject/26952826/)
|
||||
|
|
42
README.md
|
@ -3,13 +3,12 @@
|
|||
| 算法[:pencil2:](#pencil2-算法) | 操作系统[:computer:](#computer-操作系统)|网络[:cloud:](#cloud-网络) | 面向对象[:couple:](#couple-面向对象) |数据库[:floppy_disk:](#floppy_disk-数据库)| Java [:coffee:](#coffee-java)| 系统设计[:bulb:](#bulb-系统设计)| 工具[:hammer:](#hammer-工具)| 编码实践[:speak_no_evil:](#speak_no_evil-编码实践)| 后记[:memo:](#memo-后记) |
|
||||
|
||||
<br>
|
||||
|
||||
<div align="center">
|
||||
<img src="other/LogoMakr_0zpEzN.png" width="150px">
|
||||
<br>
|
||||
<a href="other/Group.md"> <img src="https://img.shields.io/badge/>-group-4ab8a1.svg"></a> <a href="https://legacy.gitbook.com/book/cyc2018/interview-notebook/details"> <img src="https://img.shields.io/badge/_-gitbook-4ab8a1.svg"></a>
|
||||
</div>
|
||||
|
||||
<!-- [![](https://img.shields.io/badge/>-gitter-blue.svg)](https://gitter.im/CyC2018-Interview-Notebook/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) [![](https://img.shields.io/badge/_-gitbook-4ab8a1.svg)](https://legacy.gitbook.com/book/cyc2018/interview-notebook/details) -->
|
||||
</div>
|
||||
|
||||
### :pencil2: 算法
|
||||
|
||||
|
@ -89,7 +88,7 @@
|
|||
|
||||
- [Java 容器](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20容器.md)
|
||||
|
||||
源码分析:ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeekHashMap。
|
||||
源码分析:ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeakHashMap。
|
||||
|
||||
- [Java 并发](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20并发.md)
|
||||
|
||||
|
@ -169,10 +168,6 @@
|
|||
|
||||
学习笔记不是从网上到处拼凑而来,除了少部分引用书上和技术文档的原文,其余都是笔者的原创。在您引用本仓库内容或者对内容进行修改演绎时,请遵循文末的开源协议,谢谢。
|
||||
|
||||
#### BookList
|
||||
|
||||
本仓库参考的书目:[BOOKLIST](https://github.com/CyC2018/Interview-Notebook/blob/master/BOOKLIST.md)。
|
||||
|
||||
#### How To Contribute
|
||||
|
||||
笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接对相应文档进行编辑修改。
|
||||
|
@ -181,9 +176,13 @@
|
|||
|
||||
欢迎在 Issue 中提交对本仓库的改进建议~
|
||||
|
||||
#### BookList
|
||||
|
||||
本仓库参考的书目:[BOOKLIST](https://github.com/CyC2018/Interview-Notebook/blob/master/BOOKLIST.md)。
|
||||
|
||||
#### Typesetting
|
||||
|
||||
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
|
||||
笔记内容按照 [中文文案排版指北](https://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
|
||||
|
||||
笔记不使用 `![]()` 这种方式来引用图片,而是用 `<img>` 标签。一方面是为了能够控制图片以合适的大小显示,另一方面是因为 GFM 不支持 `<center> ![]() </center>` 让图片居中显示,只能使用 `<div align="center"> <img src=""/> </div>` 达到居中的效果。
|
||||
|
||||
|
@ -197,38 +196,41 @@
|
|||
|
||||
笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github,或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
|
||||
|
||||
#### Statement
|
||||
|
||||
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
|
||||
|
||||
#### Logo
|
||||
|
||||
Power by [logomakr](https://logomakr.com/).
|
||||
|
||||
#### Statement
|
||||
|
||||
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
|
||||
|
||||
#### Acknowledgements
|
||||
|
||||
感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与笔者联系。
|
||||
|
||||
<a href="https://github.com/linw7">
|
||||
<img src="other/21679154.png" width="50px">
|
||||
<img src="https://avatars3.githubusercontent.com/u/21679154?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/g10guang">
|
||||
<img src="other/18458140.jpg" width="50px">
|
||||
<img src="https://avatars1.githubusercontent.com/u/18458140?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/ResolveWang">
|
||||
<img src="other/8018776.jpg" width="50px">
|
||||
<img src="https://avatars1.githubusercontent.com/u/8018776?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/crossoverJie">
|
||||
<img src="other/15684156.jpg" width="50px">
|
||||
<img src="https://avatars1.githubusercontent.com/u/15684156?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/jy03078584">
|
||||
<img src="other/7719370.png" width="50px">
|
||||
<img src="https://avatars2.githubusercontent.com/u/7719370?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/kwongtailau">
|
||||
<img src="other/22954582.jpg" width="50px">
|
||||
<img src="https://avatars0.githubusercontent.com/u/22954582?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/xiangflight">
|
||||
<img src="other/10072416.jpg" width="50px">
|
||||
<img src="https://avatars2.githubusercontent.com/u/10072416?s=400&v=4" width="50px">
|
||||
</a>
|
||||
<a href="https://github.com/mafulong">
|
||||
<img src="https://avatars1.githubusercontent.com/u/24795000?s=400&v=4" width="50px">
|
||||
</a>
|
||||
|
||||
#### License
|
||||
|
|
27
SUMMARY.md
|
@ -1,4 +1,4 @@
|
|||
This file used to generate gitbook catalogue.
|
||||
This file used to generate gitbook catalogue.
|
||||
|
||||
# Summary
|
||||
|
||||
|
@ -27,10 +27,23 @@ This file used to generate gitbook catalogue.
|
|||
* [Java 虚拟机](/notes/Java 虚拟机.md)
|
||||
* [Java 并发](/notes/Java 并发.md)
|
||||
* [Java 容器](/notes/Java 容器.md)
|
||||
* [Java I/O](/notes/Java I/O.md)
|
||||
* 分布式
|
||||
* [一致性](/notes/一致性.md)
|
||||
* [分布式问题分析](/notes/分布式问题分析.md)
|
||||
|
||||
|
||||
* [Java I/O](/notes/Java IO.md)
|
||||
* 系统设计
|
||||
* [系统设计基础](/notes/系统设计基础.md)
|
||||
* [分布式](/notes/分布式.md)
|
||||
* [集群](/notes/集群.md)
|
||||
* [攻击技术](/notes/攻击技术.md)
|
||||
* [缓存](/notes/缓存.md)
|
||||
* [消息队列](/notes/消息队列.md)
|
||||
* 工具
|
||||
* [Git](/notes/Git.md)
|
||||
* [Docker](/notes/Docker.md)
|
||||
* [正则表达式](/notes/正则表达式.md)
|
||||
* [构建工具](/notes/构建工具.md)
|
||||
* 编码实践
|
||||
* [重构](/notes/重构.md)
|
||||
* [代码可读性](/notes/代码可读性.md)
|
||||
* [代码风格规范](/notes/代码风格规范.md)
|
||||
* 参考书目
|
||||
* [BOOKLIST](/BOOKLIST.md)
|
||||
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<!-- GFM-TOC -->
|
||||
* [0. 进程内存空间中,堆和栈的区别](#0-进程内存空间中,堆和栈的区别)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 0. 进程内存空间中,堆和栈的区别
|
||||
|
||||
> C++
|
||||
|
||||
堆:动态、malloc()、new、链式分配、向上生长;栈:函数调用、编译器分配回收、向下生长。
|
||||
|
||||
https://www.cnblogs.com/sunziying/p/6510030.html
|
||||
|
||||
By @CyC
|
||||
|
||||
---
|
|
@ -44,14 +44,14 @@ Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master
|
|||
|
||||
- git add files 把文件的修改添加到暂存区
|
||||
- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了
|
||||
- git reset -- files 使用当前分支上的修改覆盖暂缓区,用来撤销最后一次 git add files
|
||||
- git reset -- files 使用当前分支上的修改覆盖暂存区,用来撤销最后一次 git add files
|
||||
- git checkout -- files 使用暂存区的修改覆盖工作目录,用来撤销本地修改
|
||||
|
||||
<div align="center"> <img src="../pics//17976404-95f5-480e-9cb4-250e6aa1d55f.png"/> </div><br>
|
||||
|
||||
可以跳过暂存区域直接从分支中取出修改,或者直接提交修改到分支中。
|
||||
|
||||
- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交
|
||||
- git commit -a 直接把所有文件的修改添加到暂存区然后执行提交
|
||||
- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作
|
||||
|
||||
# 分支实现
|
||||
|
|
120
notes/HTTP.md
|
@ -24,10 +24,9 @@
|
|||
* [响应首部字段](#响应首部字段)
|
||||
* [实体首部字段](#实体首部字段)
|
||||
* [五、具体应用](#五具体应用)
|
||||
* [Cookie](#cookie)
|
||||
* [6. Secure](#6-secure)
|
||||
* [缓存](#缓存)
|
||||
* [连接管理](#连接管理)
|
||||
* [Cookie](#cookie)
|
||||
* [缓存](#缓存)
|
||||
* [内容协商](#内容协商)
|
||||
* [内容编码](#内容编码)
|
||||
* [范围请求](#范围请求)
|
||||
|
@ -40,20 +39,19 @@
|
|||
* [认证](#认证)
|
||||
* [完整性保护](#完整性保护)
|
||||
* [HTTPs 的缺点](#https-的缺点)
|
||||
* [配置 HTTPs](#配置-https)
|
||||
* [七、HTTP/2.0](#七http20)
|
||||
* [HTTP/1.x 缺陷](#http1x-缺陷)
|
||||
* [二进制分帧层](#二进制分帧层)
|
||||
* [服务端推送](#服务端推送)
|
||||
* [首部压缩](#首部压缩)
|
||||
* [八、GET 和 POST 比较](#八get-和-post-比较)
|
||||
* [八、HTTP/1.1 新特性](#八http11-新特性)
|
||||
* [九、GET 和 POST 比较](#九get-和-post-比较)
|
||||
* [作用](#作用)
|
||||
* [参数](#参数)
|
||||
* [安全](#安全)
|
||||
* [幂等性](#幂等性)
|
||||
* [可缓存](#可缓存)
|
||||
* [XMLHttpRequest](#xmlhttprequest)
|
||||
* [九、HTTP/1.0 与 HTTP/1.1 的区别](#九http10-与-http11-的区别)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
@ -104,7 +102,7 @@ URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基
|
|||
|
||||
POST 主要用来传输数据,而 GET 主要用来获取资源。
|
||||
|
||||
更多 POST 与 GET 的比较请见第八章。
|
||||
更多 POST 与 GET 的比较请见第九章。
|
||||
|
||||
## PUT
|
||||
|
||||
|
@ -305,6 +303,25 @@ CONNECT www.example.com:443 HTTP/1.1
|
|||
|
||||
# 五、具体应用
|
||||
|
||||
## 连接管理
|
||||
|
||||
<div align="center"> <img src="../pics//HTTP1_x_Connections.png" width="800"/> </div><br>
|
||||
|
||||
### 1. 短连接与长连接
|
||||
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
|
||||
|
||||
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
|
||||
|
||||
- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`;
|
||||
- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。
|
||||
|
||||
### 2. 流水线
|
||||
|
||||
默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
|
||||
|
||||
流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
|
||||
|
||||
## Cookie
|
||||
|
||||
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
|
||||
|
@ -361,7 +378,7 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径
|
|||
|
||||
### 5. JavaScript
|
||||
|
||||
通过 `Document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。
|
||||
通过 `document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。
|
||||
|
||||
```html
|
||||
document.cookie = "yummy_cookie=choco";
|
||||
|
@ -369,19 +386,19 @@ document.cookie = "tasty_cookie=strawberry";
|
|||
console.log(document.cookie);
|
||||
```
|
||||
|
||||
### 6. HttpOnly
|
||||
### 6. HttpOnly
|
||||
|
||||
标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `Document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
|
||||
标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
|
||||
|
||||
```html
|
||||
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
|
||||
```
|
||||
|
||||
## 6. Secure
|
||||
### 7. Secure
|
||||
|
||||
标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
|
||||
|
||||
### 7. Session
|
||||
### 8. Session
|
||||
|
||||
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
|
||||
|
||||
|
@ -396,11 +413,11 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
|
|||
|
||||
应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下,例如转账等操作,除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
|
||||
|
||||
### 8. 浏览器禁用 Cookie
|
||||
### 9. 浏览器禁用 Cookie
|
||||
|
||||
此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外,不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
|
||||
|
||||
### 9. Cookie 与 Session 选择
|
||||
### 10. Cookie 与 Session 选择
|
||||
|
||||
- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session;
|
||||
- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
|
||||
|
@ -422,7 +439,7 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
|
|||
|
||||
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
|
||||
|
||||
**(一)禁止进行缓存**
|
||||
**3.1 禁止进行缓存**
|
||||
|
||||
no-store 指令规定不能对请求或响应的任何一部分进行缓存。
|
||||
|
||||
|
@ -430,7 +447,7 @@ no-store 指令规定不能对请求或响应的任何一部分进行缓存。
|
|||
Cache-Control: no-store
|
||||
```
|
||||
|
||||
**(二)强制确认缓存**
|
||||
**3.2 强制确认缓存**
|
||||
|
||||
no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。
|
||||
|
||||
|
@ -438,7 +455,7 @@ no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源
|
|||
Cache-Control: no-cache
|
||||
```
|
||||
|
||||
**(三)私有缓存和公共缓存**
|
||||
**3.3 私有缓存和公共缓存**
|
||||
|
||||
private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。
|
||||
|
||||
|
@ -452,7 +469,7 @@ public 指令规定了将资源作为公共缓存,可以被多个用户所使
|
|||
Cache-Control: public
|
||||
```
|
||||
|
||||
**(四)缓存过期机制**
|
||||
**3.4 缓存过期机制**
|
||||
|
||||
max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
|
||||
|
||||
|
@ -495,34 +512,15 @@ Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
|
|||
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
|
||||
```
|
||||
|
||||
## 连接管理
|
||||
|
||||
<div align="center"> <img src="../pics//HTTP1_x_Connections.png" width="800"/> </div><br>
|
||||
|
||||
### 1. 短连接与长连接
|
||||
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
|
||||
|
||||
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
|
||||
|
||||
- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`;
|
||||
- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`。
|
||||
|
||||
### 2. 流水线
|
||||
|
||||
默认情况下,HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
|
||||
|
||||
流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
|
||||
|
||||
## 内容协商
|
||||
|
||||
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
|
||||
|
||||
### 1. 类型
|
||||
|
||||
**(一)服务端驱动型**
|
||||
**1.1 服务端驱动型**
|
||||
|
||||
客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Languag,服务器根据这些字段返回特定的资源。
|
||||
客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language,服务器根据这些字段返回特定的资源。
|
||||
|
||||
它存在以下问题:
|
||||
|
||||
|
@ -530,7 +528,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
|
|||
- 客户端提供的信息相当冗长(HTTP/2 协议的首部压缩机制缓解了这个问题),并且存在隐私风险(HTTP 指纹识别技术);
|
||||
- 给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。
|
||||
|
||||
**(二)代理驱动型**
|
||||
**1.2 代理驱动型**
|
||||
|
||||
服务器返回 300 Multiple Choices 或者 406 Not Acceptable,客户端从中选出最合适的那个资源。
|
||||
|
||||
|
@ -720,11 +718,6 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
|
|||
|
||||
- 因为需要进行加密解密等过程,因此速度会更慢;
|
||||
- 需要支付证书授权的高额费用。
|
||||
|
||||
## 配置 HTTPs
|
||||
|
||||
[Nginx 配置 HTTPS 服务器](https://aotu.io/notes/2016/08/16/nginx-https/index.html)
|
||||
|
||||
# 七、HTTP/2.0
|
||||
|
||||
## HTTP/1.x 缺陷
|
||||
|
@ -765,7 +758,25 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
|
|||
|
||||
<div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br>
|
||||
|
||||
# 八、GET 和 POST 比较
|
||||
# 八、HTTP/1.1 新特性
|
||||
|
||||
详细内容请见上文
|
||||
|
||||
- 默认是长连接
|
||||
|
||||
- 支持流水线
|
||||
|
||||
- 支持同时打开多个 TCP 连接
|
||||
|
||||
- 支持虚拟主机
|
||||
|
||||
- 新增状态码 100
|
||||
|
||||
- 支持分块传输编码
|
||||
|
||||
- 新增缓存处理指令 max-age
|
||||
|
||||
# 九、GET 和 POST 比较
|
||||
|
||||
## 作用
|
||||
|
||||
|
@ -847,23 +858,6 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
|
|||
- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。
|
||||
- 而 GET 方法 Header 和 Data 会一起发送。
|
||||
|
||||
# 九、HTTP/1.0 与 HTTP/1.1 的区别
|
||||
|
||||
> 详细内容请见上文
|
||||
|
||||
- HTTP/1.1 默认是长连接
|
||||
|
||||
- HTTP/1.1 支持管线化处理
|
||||
|
||||
- HTTP/1.1 支持同时打开多个 TCP 连接
|
||||
|
||||
- HTTP/1.1 支持虚拟主机
|
||||
|
||||
- HTTP/1.1 新增状态码 100
|
||||
|
||||
- HTTP/1.1 支持分块传输编码
|
||||
|
||||
- HTTP/1.1 新增缓存处理指令 max-age
|
||||
|
||||
|
||||
# 参考资料
|
||||
|
|
|
@ -63,22 +63,25 @@ public static void listAllFiles(File dir) {
|
|||
}
|
||||
```
|
||||
|
||||
从 Java7 开始,可以使用 Paths 和 Files 代替 File。
|
||||
|
||||
# 三、字节操作
|
||||
|
||||
## 实现文件复制
|
||||
|
||||
```java
|
||||
public static void copyFile(String src, String dist) throws IOException {
|
||||
|
||||
FileInputStream in = new FileInputStream(src);
|
||||
FileOutputStream out = new FileOutputStream(dist);
|
||||
|
||||
byte[] buffer = new byte[20 * 1024];
|
||||
int cnt;
|
||||
|
||||
// read() 最多读取 buffer.length 个字节
|
||||
// 返回的是实际读取的个数
|
||||
// 返回 -1 的时候表示读到 eof,即文件尾
|
||||
while (in.read(buffer, 0, buffer.length) != -1) {
|
||||
out.write(buffer);
|
||||
while ((cnt = in.read(buffer, 0, buffer.length)) != -1) {
|
||||
out.write(buffer, 0, cnt);
|
||||
}
|
||||
|
||||
in.close();
|
||||
|
@ -119,7 +122,7 @@ DataInputStream 装饰者提供了对更多数据类型进行输入的操作,
|
|||
|
||||
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
|
||||
|
||||
Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
|
||||
Java 的内存编码使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
|
||||
|
||||
## String 的编码方式
|
||||
|
||||
|
@ -182,8 +185,10 @@ public static void readFileContent(String filePath) throws IOException {
|
|||
|
||||
```java
|
||||
public static void main(String[] args) throws IOException, ClassNotFoundException {
|
||||
|
||||
A a1 = new A(123, "abc");
|
||||
String objectFile = "file/a1";
|
||||
|
||||
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
|
||||
objectOutputStream.writeObject(a1);
|
||||
objectOutputStream.close();
|
||||
|
@ -195,6 +200,7 @@ public static void main(String[] args) throws IOException, ClassNotFoundExceptio
|
|||
}
|
||||
|
||||
private static class A implements Serializable {
|
||||
|
||||
private int x;
|
||||
private String y;
|
||||
|
||||
|
@ -280,10 +286,6 @@ public static void main(String[] args) throws IOException {
|
|||
|
||||
# 七、NIO
|
||||
|
||||
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
|
||||
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
|
||||
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
|
||||
|
||||
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
|
||||
|
||||
## 流与块
|
||||
|
@ -339,7 +341,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
|||
|
||||
<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
|
||||
|
||||
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5,limit 保持不变。
|
||||
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。
|
||||
|
||||
<div align="center"> <img src="../pics//80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br>
|
||||
|
||||
|
@ -608,8 +610,10 @@ NIO 与普通 I/O 的区别主要有以下两点:
|
|||
|
||||
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
|
||||
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
|
||||
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
|
||||
- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
|
||||
- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.htm)
|
||||
- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html)
|
||||
- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html)
|
||||
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
|
||||
- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document)
|
||||
|
|
|
@ -196,9 +196,9 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
|
|||
|
||||
## String Pool
|
||||
|
||||
字符串常量池(String Poll)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Poll 中。
|
||||
字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。
|
||||
|
||||
当一个字符串调用 intern() 方法时,如果 String Poll 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Poll 中字符串的引用;否则,就会在 String Poll 中添加一个新的字符串,并返回这个新字符串的引用。
|
||||
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
|
||||
|
||||
下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。
|
||||
|
||||
|
@ -216,19 +216,19 @@ System.out.println(s3 == s4); // true
|
|||
```java
|
||||
String s5 = "bbb";
|
||||
String s6 = "bbb";
|
||||
System.out.println(s4 == s5); // true
|
||||
System.out.println(s5 == s6); // true
|
||||
```
|
||||
|
||||
在 Java 7 之前,String Poll 被放在运行时常量池中,它属于永久代。而在 Java 7,String Poll 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
|
||||
在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
|
||||
|
||||
- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
|
||||
- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html)
|
||||
|
||||
## new String("abc")
|
||||
|
||||
使用这种方式一共会创建两个字符串对象(前提是 String Poll 中还没有 "abc" 字符串对象)。
|
||||
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。
|
||||
|
||||
- "abc" 属于字符串字面量,因此编译时期会在 String Poll 中创建一个字符串对象,指向这个 "abc" 字符串字面量;
|
||||
- "abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量;
|
||||
- 而使用 new 的方式会在堆中创建一个字符串对象。
|
||||
|
||||
创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。
|
||||
|
@ -267,7 +267,7 @@ Constant pool:
|
|||
// ...
|
||||
```
|
||||
|
||||
在 Constant Poll 中,#19 存储这字符串字面量 "abc",#3 是 String Poll 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Poll 中的字符串对象作为 String 构造函数的参数。
|
||||
在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。
|
||||
|
||||
以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。
|
||||
|
||||
|
@ -284,10 +284,11 @@ public String(String original) {
|
|||
|
||||
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
|
||||
|
||||
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。
|
||||
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,在一方改变其所指向对象的内容时对另一方没有影响。
|
||||
|
||||
```java
|
||||
public class Dog {
|
||||
|
||||
String name;
|
||||
|
||||
Dog(String name) {
|
||||
|
@ -327,7 +328,7 @@ public class PassByValueExample {
|
|||
}
|
||||
```
|
||||
|
||||
但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。
|
||||
如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。
|
||||
|
||||
```java
|
||||
class PassByValueExample {
|
||||
|
@ -347,7 +348,9 @@ class PassByValueExample {
|
|||
|
||||
## float 与 double
|
||||
|
||||
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。Java 不能隐式执行向下转型,因为这会使得精度降低。
|
||||
Java 不能隐式执行向下转型,因为这会使得精度降低。
|
||||
|
||||
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
|
||||
|
||||
```java
|
||||
// float f = 1.1;
|
||||
|
@ -368,10 +371,11 @@ short s1 = 1;
|
|||
// s1 = s1 + 1;
|
||||
```
|
||||
|
||||
但是使用 += 运算符可以执行隐式类型转换。
|
||||
但是使用 += 或者 ++ 运算符可以执行隐式类型转换。
|
||||
|
||||
```java
|
||||
s1 += 1;
|
||||
// s1++;
|
||||
```
|
||||
|
||||
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
|
||||
|
@ -431,7 +435,7 @@ protected 用于修饰成员,表示在继承体系中成员对于子类可见
|
|||
|
||||
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
|
||||
|
||||
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 共有字段,如果在某个时刻,我们想要使用 int 去存储 id 字段,那么就需要去修改所有的客户端代码。
|
||||
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中,AccessExample 拥有 id 公有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
|
||||
|
||||
```java
|
||||
public class AccessExample {
|
||||
|
@ -587,10 +591,11 @@ System.out.println(InterfaceExample.x);
|
|||
## super
|
||||
|
||||
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
|
||||
- 访问父类的成员:如果子类重写了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
|
||||
|
||||
```java
|
||||
public class SuperExample {
|
||||
|
||||
protected int x;
|
||||
protected int y;
|
||||
|
||||
|
@ -607,6 +612,7 @@ public class SuperExample {
|
|||
|
||||
```java
|
||||
public class SuperExtendExample extends SuperExample {
|
||||
|
||||
private int z;
|
||||
|
||||
public SuperExtendExample(int x, int y, int z) {
|
||||
|
@ -658,7 +664,6 @@ SuperExtendExample.func()
|
|||
## 概览
|
||||
|
||||
```java
|
||||
public final native Class<?> getClass()
|
||||
|
||||
public native int hashCode()
|
||||
|
||||
|
@ -668,6 +673,10 @@ protected native Object clone() throws CloneNotSupportedException
|
|||
|
||||
public String toString()
|
||||
|
||||
public final native Class<?> getClass()
|
||||
|
||||
protected void finalize() throws Throwable {}
|
||||
|
||||
public final native void notify()
|
||||
|
||||
public final native void notifyAll()
|
||||
|
@ -677,34 +686,32 @@ public final native void wait(long timeout) throws InterruptedException
|
|||
public final void wait(long timeout, int nanos) throws InterruptedException
|
||||
|
||||
public final void wait() throws InterruptedException
|
||||
|
||||
protected void finalize() throws Throwable {}
|
||||
```
|
||||
|
||||
## equals()
|
||||
|
||||
**1. 等价关系**
|
||||
|
||||
(一)自反性
|
||||
Ⅰ 自反性
|
||||
|
||||
```java
|
||||
x.equals(x); // true
|
||||
```
|
||||
|
||||
(二)对称性
|
||||
Ⅱ 对称性
|
||||
|
||||
```java
|
||||
x.equals(y) == y.equals(x); // true
|
||||
```
|
||||
|
||||
(三)传递性
|
||||
Ⅲ 传递性
|
||||
|
||||
```java
|
||||
if (x.equals(y) && y.equals(z))
|
||||
x.equals(z); // true;
|
||||
```
|
||||
|
||||
(四)一致性
|
||||
Ⅳ 一致性
|
||||
|
||||
多次调用 equals() 方法结果不变
|
||||
|
||||
|
@ -712,7 +719,7 @@ if (x.equals(y) && y.equals(z))
|
|||
x.equals(y) == x.equals(y); // true
|
||||
```
|
||||
|
||||
(五)与 null 的比较
|
||||
Ⅴ 与 null 的比较
|
||||
|
||||
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
|
||||
|
||||
|
@ -741,6 +748,7 @@ System.out.println(x == y); // false
|
|||
|
||||
```java
|
||||
public class EqualExample {
|
||||
|
||||
private int x;
|
||||
private int y;
|
||||
private int z;
|
||||
|
@ -804,6 +812,7 @@ public int hashCode() {
|
|||
|
||||
```java
|
||||
public class ToStringExample {
|
||||
|
||||
private int number;
|
||||
|
||||
public ToStringExample(int number) {
|
||||
|
@ -847,7 +856,7 @@ public class CloneExample {
|
|||
private int b;
|
||||
|
||||
@Override
|
||||
protected CloneExample clone() throws CloneNotSupportedException {
|
||||
public CloneExample clone() throws CloneNotSupportedException {
|
||||
return (CloneExample)super.clone();
|
||||
}
|
||||
}
|
||||
|
@ -876,7 +885,7 @@ public class CloneExample implements Cloneable {
|
|||
private int b;
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
return super.clone();
|
||||
}
|
||||
}
|
||||
|
@ -1050,6 +1059,7 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和
|
|||
|
||||
```java
|
||||
public class A {
|
||||
|
||||
private int x; // 实例变量
|
||||
private static int y; // 静态变量
|
||||
|
||||
|
@ -1078,6 +1088,7 @@ public abstract class A {
|
|||
|
||||
```java
|
||||
public class A {
|
||||
|
||||
private static int x;
|
||||
private int y;
|
||||
|
||||
|
@ -1116,6 +1127,7 @@ public class A {
|
|||
|
||||
```java
|
||||
public class OuterClass {
|
||||
|
||||
class InnerClass {
|
||||
}
|
||||
|
||||
|
@ -1197,19 +1209,21 @@ Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect
|
|||
- **Method** :可以使用 invoke() 方法调用与 Method 对象关联的方法;
|
||||
- **Constructor** :可以用 Constructor 创建新的对象。
|
||||
|
||||
**Advantages of Using Reflection:**
|
||||
**反射的优点:**
|
||||
|
||||
- **Extensibility Features** : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
|
||||
- **Class Browsers and Visual Development Environments** : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
|
||||
- **Debuggers and Test Tools** : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
|
||||
* **可扩展性** :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
|
||||
* **类浏览器和可视化开发环境** :一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
|
||||
* **调试器和测试工具** : 调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
|
||||
|
||||
**Drawbacks of Reflection:**
|
||||
**反射的缺点:**
|
||||
|
||||
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.
|
||||
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。
|
||||
|
||||
- **Performance Overhead** : Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
|
||||
- **Security Restrictions** : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
|
||||
- **Exposure of Internals** :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.
|
||||
* **性能开销** :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
|
||||
|
||||
* **安全限制** :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
|
||||
|
||||
* **内部暴露** :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
|
||||
|
||||
|
||||
- [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* [HashMap](#hashmap)
|
||||
* [ConcurrentHashMap](#concurrenthashmap)
|
||||
* [LinkedHashMap](#linkedhashmap)
|
||||
* [WeekHashMap](#weekhashmap)
|
||||
* [WeakHashMap](#weakhashmap)
|
||||
* [附录](#附录)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
@ -25,7 +25,7 @@
|
|||
|
||||
## Collection
|
||||
|
||||
<div align="center"> <img src="../pics//VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//VP6n3i8W48Ptde8NQ9_0eSR5eOD6uqx.png"/> </div><br>
|
||||
|
||||
### 1. Set
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
|||
|
||||
<div align="center"> <img src="../pics//SoWkIImgAStDuUBAp2j9BKfBJ4vLy0G.png"/> </div><br>
|
||||
|
||||
Collection 实现了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
|
||||
Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
|
||||
|
||||
从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。
|
||||
|
||||
|
@ -100,7 +100,7 @@ List list = Arrays.asList(arr);
|
|||
也可以使用以下方式调用 asList():
|
||||
|
||||
```java
|
||||
List list = Arrays.asList(1,2,3);
|
||||
List list = Arrays.asList(1, 2, 3);
|
||||
```
|
||||
|
||||
# 三、源码分析
|
||||
|
@ -575,7 +575,7 @@ int hash = hash(key);
|
|||
int i = indexFor(hash, table.length);
|
||||
```
|
||||
|
||||
(一)计算 hash 值
|
||||
**4.1 计算 hash 值**
|
||||
|
||||
```java
|
||||
final int hash(Object k) {
|
||||
|
@ -600,7 +600,7 @@ public final int hashCode() {
|
|||
}
|
||||
```
|
||||
|
||||
(二)取模
|
||||
**4.2 取模**
|
||||
|
||||
令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:
|
||||
|
||||
|
@ -646,8 +646,8 @@ static int indexFor(int h, int length) {
|
|||
| 参数 | 含义 |
|
||||
| :--: | :-- |
|
||||
| capacity | table 的容量大小,默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。|
|
||||
| size | table 的实际使用量。 |
|
||||
| threshold | size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。 |
|
||||
| size | 键值对数量。 |
|
||||
| threshold | size 的临界值,当 size 大于等于 threshold 就必须进行扩容操作。 |
|
||||
| loadFactor | 装载因子,table 能够使用的比例,threshold = capacity * loadFactor。|
|
||||
|
||||
```java
|
||||
|
@ -730,7 +730,7 @@ new capacity : 00100000
|
|||
- 它的哈希值如果在第 5 位上为 0,那么取模得到的结果和之前一样;
|
||||
- 如果为 1,那么得到的结果为原来的结果 +16。
|
||||
|
||||
### 7. 扩容-计算数组容量
|
||||
### 7. 计算数组容量
|
||||
|
||||
HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。
|
||||
|
||||
|
@ -738,7 +738,7 @@ HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它
|
|||
|
||||
```
|
||||
mask |= mask >> 1 11011000
|
||||
mask |= mask >> 2 11111100
|
||||
mask |= mask >> 2 11111110
|
||||
mask |= mask >> 4 11111111
|
||||
```
|
||||
|
||||
|
@ -974,7 +974,7 @@ void afterNodeAccess(Node<K,V> e) { // move node to last
|
|||
|
||||
### afterNodeInsertion()
|
||||
|
||||
在 put 等操作之后执行,当 removeEldestEntry() 方法返回 ture 时会移除最晚的节点,也就是链表首部节点 first。
|
||||
在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。
|
||||
|
||||
evict 只有在构建 Map 的时候才为 false,在这里为 true。
|
||||
|
||||
|
@ -1034,7 +1034,7 @@ public static void main(String[] args) {
|
|||
[3, 1, 4]
|
||||
```
|
||||
|
||||
## WeekHashMap
|
||||
## WeakHashMap
|
||||
|
||||
### 存储结构
|
||||
|
||||
|
|
124
notes/Java 并发.md
|
@ -44,9 +44,10 @@
|
|||
* [内存模型三大特性](#内存模型三大特性)
|
||||
* [先行发生原则](#先行发生原则)
|
||||
* [十一、线程安全](#十一线程安全)
|
||||
* [线程安全定义](#线程安全定义)
|
||||
* [线程安全分类](#线程安全分类)
|
||||
* [线程安全的实现方法](#线程安全的实现方法)
|
||||
* [不可变](#不可变)
|
||||
* [互斥同步](#互斥同步)
|
||||
* [非阻塞同步](#非阻塞同步)
|
||||
* [无同步方案](#无同步方案)
|
||||
* [十二、锁优化](#十二锁优化)
|
||||
* [自旋锁](#自旋锁)
|
||||
* [锁消除](#锁消除)
|
||||
|
@ -631,7 +632,7 @@ B
|
|||
|
||||
它们都属于 Object 的一部分,而不属于 Thread。
|
||||
|
||||
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。
|
||||
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。
|
||||
|
||||
使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
|
||||
|
||||
|
@ -739,6 +740,7 @@ java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J.
|
|||
|
||||
```java
|
||||
public class CountdownLatchExample {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
final int totalThread = 10;
|
||||
CountDownLatch countDownLatch = new CountDownLatch(totalThread);
|
||||
|
@ -764,7 +766,7 @@ run..run..run..run..run..run..run..run..run..run..end
|
|||
|
||||
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
|
||||
|
||||
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 awati() 方法而在等待的线程才能继续执行。
|
||||
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。
|
||||
|
||||
CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。
|
||||
|
||||
|
@ -787,6 +789,7 @@ public CyclicBarrier(int parties) {
|
|||
|
||||
```java
|
||||
public class CyclicBarrierExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final int totalThread = 10;
|
||||
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
|
||||
|
@ -821,6 +824,7 @@ Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的
|
|||
|
||||
```java
|
||||
public class SemaphoreExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final int clientCount = 3;
|
||||
final int totalRequestCount = 10;
|
||||
|
@ -865,6 +869,7 @@ FutureTask 可用于异步获取执行结果或取消执行任务的场景。当
|
|||
|
||||
```java
|
||||
public class FutureTaskExample {
|
||||
|
||||
public static void main(String[] args) throws ExecutionException, InterruptedException {
|
||||
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
|
||||
@Override
|
||||
|
@ -970,6 +975,7 @@ produce..produce..consume..consume..produce..consume..produce..consume..produce.
|
|||
|
||||
```java
|
||||
public class ForkJoinExample extends RecursiveTask<Integer> {
|
||||
|
||||
private final int threshold = 5;
|
||||
private int first;
|
||||
private int last;
|
||||
|
@ -1274,19 +1280,13 @@ Thread 对象的结束先行发生于 join() 方法返回。
|
|||
|
||||
# 十一、线程安全
|
||||
|
||||
## 线程安全定义
|
||||
多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为。
|
||||
|
||||
一个类在可以被多个线程安全调用时就是线程安全的。
|
||||
线程安全有以下几种实现方式:
|
||||
|
||||
## 线程安全分类
|
||||
## 不可变
|
||||
|
||||
线程安全不是一个非真即假的命题,可以将共享数据按照安全程度的强弱顺序分成以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。
|
||||
|
||||
### 1. 不可变
|
||||
|
||||
不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。
|
||||
|
||||
多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
|
||||
不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
|
||||
|
||||
不可变的类型:
|
||||
|
||||
|
@ -1321,99 +1321,23 @@ public V put(K key, V value) {
|
|||
}
|
||||
```
|
||||
|
||||
### 2. 绝对线程安全
|
||||
|
||||
不管运行时环境如何,调用者都不需要任何额外的同步措施。
|
||||
|
||||
### 3. 相对线程安全
|
||||
|
||||
相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施。但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
|
||||
|
||||
在 Java 语言中,大部分的线程安全类都属于这种类型,例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。
|
||||
|
||||
对于下面的代码,如果删除元素的线程删除了 Vector 的一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。
|
||||
|
||||
```Java
|
||||
public class VectorUnsafeExample {
|
||||
private static Vector<Integer> vector = new Vector<>();
|
||||
|
||||
public static void main(String[] args) {
|
||||
while (true) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
vector.add(i);
|
||||
}
|
||||
ExecutorService executorService = Executors.newCachedThreadPool();
|
||||
executorService.execute(() -> {
|
||||
for (int i = 0; i < vector.size(); i++) {
|
||||
vector.remove(i);
|
||||
}
|
||||
});
|
||||
executorService.execute(() -> {
|
||||
for (int i = 0; i < vector.size(); i++) {
|
||||
vector.get(i);
|
||||
}
|
||||
});
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
Exception in thread "Thread-159738" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3
|
||||
at java.util.Vector.remove(Vector.java:831)
|
||||
at VectorUnsafeExample.lambda$main$0(VectorUnsafeExample.java:14)
|
||||
at VectorUnsafeExample$$Lambda$1/713338599.run(Unknown Source)
|
||||
at java.lang.Thread.run(Thread.java:745)
|
||||
```
|
||||
|
||||
|
||||
如果要保证上面的代码能正确执行下去,就需要对删除元素和获取元素的代码进行同步。
|
||||
|
||||
```java
|
||||
executorService.execute(() -> {
|
||||
synchronized (vector) {
|
||||
for (int i = 0; i < vector.size(); i++) {
|
||||
vector.remove(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
executorService.execute(() -> {
|
||||
synchronized (vector) {
|
||||
for (int i = 0; i < vector.size(); i++) {
|
||||
vector.get(i);
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 4. 线程兼容
|
||||
|
||||
线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用,我们平常说一个类不是线程安全的,绝大多数时候指的是这一种情况。Java API 中大部分的类都是属于线程兼容的,如与前面的 Vector 和 HashTable 相对应的集合类 ArrayList 和 HashMap 等。
|
||||
|
||||
### 5. 线程对立
|
||||
|
||||
线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。由于 Java 语言天生就具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。
|
||||
|
||||
## 线程安全的实现方法
|
||||
|
||||
### 1. 互斥同步
|
||||
## 互斥同步
|
||||
|
||||
synchronized 和 ReentrantLock。
|
||||
|
||||
### 2. 非阻塞同步
|
||||
## 非阻塞同步
|
||||
|
||||
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
|
||||
|
||||
互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
|
||||
|
||||
**(一)CAS**
|
||||
### 1. CAS
|
||||
|
||||
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。
|
||||
|
||||
乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。
|
||||
|
||||
**(二)AtomicInteger**
|
||||
### 2. AtomicInteger
|
||||
|
||||
J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。
|
||||
|
||||
|
@ -1450,17 +1374,17 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
|
|||
}
|
||||
```
|
||||
|
||||
**(三)ABA**
|
||||
### 3. ABA
|
||||
|
||||
如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。
|
||||
|
||||
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。
|
||||
|
||||
### 3. 无同步方案
|
||||
## 无同步方案
|
||||
|
||||
要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
|
||||
|
||||
**(一)栈封闭**
|
||||
### 1. 栈封闭
|
||||
|
||||
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
|
||||
|
||||
|
@ -1491,7 +1415,7 @@ public static void main(String[] args) {
|
|||
100
|
||||
```
|
||||
|
||||
**(二)线程本地存储(Thread Local Storage)**
|
||||
### 2. 线程本地存储(Thread Local Storage)
|
||||
|
||||
如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
|
||||
|
||||
|
@ -1597,7 +1521,7 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因
|
|||
|
||||
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
|
||||
|
||||
**(三)可重入代码(Reentrant Code)**
|
||||
### 3. 可重入代码(Reentrant Code)
|
||||
|
||||
这种代码也叫做纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
# 一、运行时数据区域
|
||||
|
||||
<div align="center"> <img src="../pics//c9ad2bf4-5580-4018-bce4-1b9a71804d9c.png" width="400"/> </div><br>
|
||||
<div align="center"> <img src="../pics//85370d54-40d1-4912-bcbe-37a2481c861d.png" width="450"/> </div><br>
|
||||
|
||||
## 程序计数器
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
|||
|
||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||
|
||||
<div align="center"> <img src="../pics//926c7438-c5e1-4b94-840a-dcb24ff1dafe.png" width="450"/> </div><br>
|
||||
<div align="center"> <img src="../pics//28ab96b4-82ea-4d99-99fb-b320f60d0a58.png" width="500"/> </div><br>
|
||||
|
||||
可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
|
||||
|
||||
|
@ -169,7 +169,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
|||
|
||||
## 引用类型
|
||||
|
||||
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
|
||||
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
|
||||
|
||||
Java 提供了四种强度不同的引用类型。
|
||||
|
||||
|
@ -227,7 +227,7 @@ obj = null;
|
|||
|
||||
<div align="center"> <img src="../pics//a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg" width=""/> </div><br>
|
||||
|
||||
将存活的对象进行标记,然后清理掉未被标记的对象。
|
||||
标记要回收的对象,然后清除。
|
||||
|
||||
不足:
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ Output: True
|
|||
Explanation: 1 * 1 + 2 * 2 = 5
|
||||
```
|
||||
|
||||
题目描述:判断一个数是否为两个数的平方和,例如 5 = 1<sup>2</sup> + 2<sup>2</sup>。
|
||||
题目描述:判断一个数是否为两个数的平方和。
|
||||
|
||||
```java
|
||||
public boolean judgeSquareSum(int c) {
|
||||
|
@ -130,7 +130,7 @@ public boolean judgeSquareSum(int c) {
|
|||
Given s = "leetcode", return "leotcede".
|
||||
```
|
||||
|
||||
使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
|
||||
使用双指针指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
|
||||
|
||||
```java
|
||||
private final static HashSet<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
|
||||
|
@ -307,6 +307,8 @@ private boolean isValid(String s, String target) {
|
|||
|
||||
[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
|
||||
|
||||
题目描述:找到第 k 大的元素。
|
||||
|
||||
**排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
|
||||
|
||||
```java
|
||||
|
@ -323,7 +325,7 @@ public int findKthLargest(int[] nums, int k) {
|
|||
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
|
||||
for (int val : nums) {
|
||||
pq.add(val);
|
||||
if (pq.size() > k) // 维护堆的大小为 K
|
||||
if (pq.size() > k) // 维护堆的大小为 K
|
||||
pq.poll();
|
||||
}
|
||||
return pq.peek();
|
||||
|
@ -555,7 +557,7 @@ Explanation: You don't need to remove any of the intervals since they're already
|
|||
|
||||
题目描述:计算让一组区间不重叠所需要移除的区间个数。
|
||||
|
||||
计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
|
||||
先计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
|
||||
|
||||
在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
|
||||
|
||||
|
@ -639,7 +641,7 @@ Output:
|
|||
|
||||
题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
|
||||
|
||||
为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。
|
||||
为了使插入操作不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入的第 k 个位置可能会变成第 k+1 个位置。
|
||||
|
||||
身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
|
||||
|
||||
|
@ -802,6 +804,52 @@ public int maxProfit(int[] prices) {
|
|||
}
|
||||
```
|
||||
|
||||
**子数组最大的和**
|
||||
|
||||
[53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/)
|
||||
|
||||
```html
|
||||
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
|
||||
the contiguous subarray [4,-1,2,1] has the largest sum = 6.
|
||||
```
|
||||
|
||||
```java
|
||||
public int maxSubArray(int[] nums) {
|
||||
if (nums == null || nums.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int preSum = nums[0];
|
||||
int maxSum = preSum;
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
preSum = preSum > 0 ? preSum + nums[i] : nums[i];
|
||||
maxSum = Math.max(maxSum, preSum);
|
||||
}
|
||||
return maxSum;
|
||||
}
|
||||
```
|
||||
|
||||
**买入和售出股票最大的收益**
|
||||
|
||||
[121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)
|
||||
|
||||
题目描述:只进行一次交易。
|
||||
|
||||
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
|
||||
|
||||
```java
|
||||
public int maxProfit(int[] prices) {
|
||||
int n = prices.length;
|
||||
if (n == 0) return 0;
|
||||
int soFarMin = prices[0];
|
||||
int max = 0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (soFarMin > prices[i]) soFarMin = prices[i];
|
||||
else max = Math.max(max, prices[i] - soFarMin);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
```
|
||||
|
||||
## 二分查找
|
||||
|
||||
**正常实现**
|
||||
|
@ -825,7 +873,7 @@ public int binarySearch(int[] nums, int key) {
|
|||
|
||||
**时间复杂度**
|
||||
|
||||
二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度都为 O(logN)。
|
||||
二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度为 O(logN)。
|
||||
|
||||
**m 计算**
|
||||
|
||||
|
@ -961,7 +1009,7 @@ public char nextGreatestLetter(char[] letters, char target) {
|
|||
[540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/)
|
||||
|
||||
```html
|
||||
Input: [1,1,2,3,3,4,4,8,8]
|
||||
Input: [1, 1, 2, 3, 3, 4, 4, 8, 8]
|
||||
Output: 2
|
||||
```
|
||||
|
||||
|
@ -1132,11 +1180,11 @@ public List<Integer> diffWaysToCompute(String input) {
|
|||
|
||||
<div align="center"> <img src="../pics//4ff355cf-9a7f-4468-af43-e5b02038facc.jpg"/> </div><br>
|
||||
|
||||
广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
|
||||
广度优先搜索一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
|
||||
|
||||
第一层:
|
||||
|
||||
- 0 -> {6,2,1,5};
|
||||
- 0 -> {6,2,1,5}
|
||||
|
||||
第二层:
|
||||
|
||||
|
@ -1150,7 +1198,7 @@ public List<Integer> diffWaysToCompute(String input) {
|
|||
- 4 -> {}
|
||||
- 3 -> {}
|
||||
|
||||
可以看到,每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 d<sub>i</sub><=d<sub>j</sub>。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。
|
||||
每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 d<sub>i</sub> <= d<sub>j</sub>。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。
|
||||
|
||||
在程序实现 BFS 时需要考虑以下问题:
|
||||
|
||||
|
@ -1180,19 +1228,17 @@ public int minPathLength(int[][] grids, int tr, int tc) {
|
|||
pathLength++;
|
||||
while (size-- > 0) {
|
||||
Pair<Integer, Integer> cur = queue.poll();
|
||||
int cr = cur.getKey(), cc = cur.getValue();
|
||||
grids[cr][cc] = 0; // 标记
|
||||
for (int[] d : direction) {
|
||||
int nr = cur.getKey() + d[0], nc = cur.getValue() + d[1];
|
||||
Pair<Integer, Integer> next = new Pair<>(nr, nc);
|
||||
if (next.getKey() < 0 || next.getValue() >= m
|
||||
|| next.getKey() < 0 || next.getValue() >= n) {
|
||||
|
||||
int nr = cr + d[0], nc = cc + d[1];
|
||||
if (nr < 0 || nr >= m || nc < 0 || nc >= n || grids[nr][nc] == 0) {
|
||||
continue;
|
||||
}
|
||||
grids[next.getKey()][next.getValue()] = 0; // 标记
|
||||
if (next.getKey() == tr && next.getValue() == tc) {
|
||||
if (nr == tr && nc == tc) {
|
||||
return pathLength;
|
||||
}
|
||||
queue.add(next);
|
||||
queue.add(new Pair<>(nr, nc));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1239,7 +1285,7 @@ public int numSquares(int n) {
|
|||
continue;
|
||||
}
|
||||
marked[next] = true;
|
||||
queue.add(cur - s);
|
||||
queue.add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1290,7 +1336,7 @@ Output: 0
|
|||
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
|
||||
```
|
||||
|
||||
找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。
|
||||
题目描述:找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。
|
||||
|
||||
```java
|
||||
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
|
||||
|
@ -1365,7 +1411,7 @@ private int getShortestPath(List<Integer>[] graphic, int start, int end) {
|
|||
|
||||
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
|
||||
|
||||
而深度优先搜索在得到一个新节点时立马对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
|
||||
而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
|
||||
|
||||
从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 **可达性** 问题。
|
||||
|
||||
|
@ -1479,12 +1525,14 @@ Input:
|
|||
[[1,1,0],
|
||||
[1,1,0],
|
||||
[0,0,1]]
|
||||
|
||||
Output: 2
|
||||
|
||||
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
|
||||
The 2nd student himself is in a friend circle. So return 2.
|
||||
```
|
||||
|
||||
好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。
|
||||
题目描述:好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。
|
||||
|
||||
```java
|
||||
private int n;
|
||||
|
@ -1530,7 +1578,7 @@ X X X X
|
|||
X O X X
|
||||
```
|
||||
|
||||
使被 'X' 包围的 'O' 转换为 'X'。
|
||||
题目描述:使被 'X' 包围的 'O' 转换为 'X'。
|
||||
|
||||
先填充最外侧,剩下的就是里侧了。
|
||||
|
||||
|
@ -1678,7 +1726,6 @@ Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
|
|||
```
|
||||
|
||||
```java
|
||||
|
||||
private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
|
||||
|
||||
public List<String> letterCombinations(String digits) {
|
||||
|
@ -2316,11 +2363,11 @@ private int cubeNum(int i, int j) {
|
|||
|
||||
一行一行地摆放,在确定一行中的那个皇后应该摆在哪一列时,需要用三个标记数组来确定某一列是否合法,这三个标记数组分别为:列标记数组、45 度对角线标记数组和 135 度对角线标记数组。
|
||||
|
||||
45 度对角线标记数组的维度为 2 \* n - 1,通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。
|
||||
45 度对角线标记数组的长度为 2 \* n - 1,通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。
|
||||
|
||||
<div align="center"> <img src="../pics//85583359-1b45-45f2-9811-4f7bb9a64db7.jpg"/> </div><br>
|
||||
|
||||
135 度对角线标记数组的维度也是 2 \* n - 1,(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。
|
||||
135 度对角线标记数组的长度也是 2 \* n - 1,(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。
|
||||
|
||||
<div align="center"> <img src="../pics//9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg"/> </div><br>
|
||||
|
||||
|
@ -2414,27 +2461,19 @@ public int climbStairs(int n) {
|
|||
|
||||
定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。
|
||||
|
||||
由于不能抢劫邻近住户,因此如果抢劫了第 i 个住户那么只能抢劫 i - 2 或者 i - 3 的住户,所以
|
||||
由于不能抢劫邻近住户,如果抢劫了第 i -1 个住户,那么就不能再抢劫第 i 个住户,所以
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=max(dp[i-2],dp[i-3])+nums[i]"/></div> <br>
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=max(dp[i-2]+nums[i],dp[i-1])"/></div> <br>
|
||||
|
||||
```java
|
||||
public int rob(int[] nums) {
|
||||
int n = nums.length;
|
||||
if (n == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (n == 1) {
|
||||
return nums[0];
|
||||
}
|
||||
int pre3 = 0, pre2 = 0, pre1 = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int cur = Math.max(pre2, pre3) + nums[i];
|
||||
pre3 = pre2;
|
||||
int pre2 = 0, pre1 = 0;
|
||||
for (int i = 0; i < nums.length; i++) {
|
||||
int cur = Math.max(pre2 + nums[i], pre1);
|
||||
pre2 = pre1;
|
||||
pre1 = cur;
|
||||
}
|
||||
return Math.max(pre1, pre2);
|
||||
return pre1;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -2443,7 +2482,7 @@ public int rob(int[] nums) {
|
|||
[213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/)
|
||||
|
||||
```java
|
||||
public int rob(int[] nums) {
|
||||
public int rob(int[] nums) {
|
||||
if (nums == null || nums.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -2454,15 +2493,14 @@ public int rob(int[] nums) {
|
|||
return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1));
|
||||
}
|
||||
|
||||
private int rob(int[] nums, int first, int last) {
|
||||
int pre3 = 0, pre2 = 0, pre1 = 0;
|
||||
private int rob(int[] nums, int first, int last) {
|
||||
int pre2 = 0, pre1 = 0;
|
||||
for (int i = first; i <= last; i++) {
|
||||
int cur = Math.max(pre3, pre2) + nums[i];
|
||||
pre3 = pre2;
|
||||
int cur = Math.max(pre1, pre2 + nums[i]);
|
||||
pre2 = pre1;
|
||||
pre1 = cur;
|
||||
}
|
||||
return Math.max(pre2, pre1);
|
||||
return pre1;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -2546,7 +2584,7 @@ public int uniquePaths(int m, int n) {
|
|||
}
|
||||
```
|
||||
|
||||
也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2,向下移动的次数 D=m-1,那么问题可以看成从 S 从取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
|
||||
也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2,向下移动的次数 D=m-1,那么问题可以看成从 S 中取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
|
||||
|
||||
```java
|
||||
public int uniquePaths(int m, int n) {
|
||||
|
@ -2574,7 +2612,7 @@ sumRange(2, 5) -> -1
|
|||
sumRange(0, 5) -> -3
|
||||
```
|
||||
|
||||
求区间 i \~ j 的和,可以转换为 sum[j] - sum[i-1],其中 sum[i] 为 0 \~ i 的和。
|
||||
求区间 i \~ j 的和,可以转换为 sum[j + 1] - sum[i],其中 sum[i] 为 0 \~ i - 1 的和。
|
||||
|
||||
```java
|
||||
class NumArray {
|
||||
|
@ -2594,30 +2632,6 @@ class NumArray {
|
|||
}
|
||||
```
|
||||
|
||||
**子数组最大的和**
|
||||
|
||||
[53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/)
|
||||
|
||||
```html
|
||||
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
|
||||
the contiguous subarray [4,-1,2,1] has the largest sum = 6.
|
||||
```
|
||||
|
||||
```java
|
||||
public int maxSubArray(int[] nums) {
|
||||
if (nums == null || nums.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
int preSum = nums[0];
|
||||
int maxSum = preSum;
|
||||
for (int i = 1; i < nums.length; i++) {
|
||||
preSum = preSum > 0 ? preSum + nums[i] : nums[i];
|
||||
maxSum = Math.max(maxSum, preSum);
|
||||
}
|
||||
return maxSum;
|
||||
}
|
||||
```
|
||||
|
||||
**数组中等差递增子区间的个数**
|
||||
|
||||
[413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/)
|
||||
|
@ -2790,7 +2804,7 @@ return ret;
|
|||
定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,
|
||||
|
||||
- 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;
|
||||
- 如果 tails[i-1] < x <= tails[i],那么更新 tails[i-1] = x。
|
||||
- 如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x。
|
||||
|
||||
例如对于数组 [4,3,6,5],有:
|
||||
|
||||
|
@ -3095,7 +3109,6 @@ public int findTargetSumWays(int[] nums, int S) {
|
|||
int W = (sum + S) / 2;
|
||||
int[] dp = new int[W + 1];
|
||||
dp[0] = 1;
|
||||
Arrays.sort(nums);
|
||||
for (int num : nums) {
|
||||
for (int i = W; i >= num; i--) {
|
||||
dp[i] = dp[i] + dp[i - num];
|
||||
|
@ -3349,27 +3362,6 @@ public int maxProfit(int[] prices, int fee) {
|
|||
}
|
||||
```
|
||||
|
||||
**买入和售出股票最大的收益**
|
||||
|
||||
[121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)
|
||||
|
||||
题目描述:只进行一次交易。
|
||||
|
||||
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
|
||||
|
||||
```java
|
||||
public int maxProfit(int[] prices) {
|
||||
int n = prices.length;
|
||||
if (n == 0) return 0;
|
||||
int soFarMin = prices[0];
|
||||
int max = 0;
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (soFarMin > prices[i]) soFarMin = prices[i];
|
||||
else max = Math.max(max, prices[i] - soFarMin);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
```
|
||||
|
||||
**只能进行两次的股票交易**
|
||||
|
||||
|
@ -7063,4 +7055,3 @@ public int[] countBits(int num) {
|
|||
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
|
||||
- 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008.
|
||||
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.
|
||||
|
||||
|
|
|
@ -1177,7 +1177,7 @@ dmtsai lines: 5 columns: 9
|
|||
- 得到 SIGCHLD 信号;
|
||||
- waitpid() 或者 wait() 调用会返回。
|
||||
|
||||
其中子进程发送的 SIGCHLD 信号包含了子进程的信息,包含了进程 ID、进程状态、进程使用 CPU 的时间等。
|
||||
其中子进程发送的 SIGCHLD 信号包含了子进程的信息,比如进程 ID、进程状态、进程使用 CPU 的时间等。
|
||||
|
||||
在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。
|
||||
|
||||
|
|
|
@ -220,7 +220,7 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
|
|||
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
|
||||
|
||||
```sql
|
||||
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
|
||||
DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
|
||||
```
|
||||
|
||||
```sql
|
||||
|
@ -316,7 +316,7 @@ FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示
|
|||
|
||||
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
|
||||
|
||||
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
|
||||
在进行存储和检索时,会保留 VARCHAR 末尾的空格,而会删除 CHAR 末尾的空格。
|
||||
|
||||
## 时间和日期
|
||||
|
||||
|
@ -374,7 +374,7 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
|
|||
|
||||
### 2. 连接
|
||||
|
||||
可以将原来的连接分解成多个单表连接查询,然后在用户程序中进行连接。
|
||||
可以将原来的连接分解成多个单表查询,然后在用户程序中进行连接。
|
||||
|
||||
### 3. ID 唯一性
|
||||
|
||||
|
|
|
@ -209,7 +209,7 @@ OK
|
|||
|
||||
## 字典
|
||||
|
||||
dictht 是一个散列表结构,使用拉链法保存哈希冲突的 dictEntry。
|
||||
dictht 是一个散列表结构,使用拉链法保存哈希冲突。
|
||||
|
||||
```c
|
||||
/* This is our hash table structure. Every dictionary has two of this as we
|
||||
|
@ -253,7 +253,7 @@ rehash 操作不是一次性完成,而是采用渐进方式,这是为了避
|
|||
|
||||
在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。
|
||||
|
||||
采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的操作也需要到对应的 dictht 去执行。
|
||||
采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的查找操作也需要到对应的 dictht 去执行。
|
||||
|
||||
```c
|
||||
/* Performs N steps of incremental rehashing. Returns 1 if there are still
|
||||
|
@ -355,7 +355,7 @@ List 是一个双向链表,可以通过 lpop 和 lpush 写入和读取消息
|
|||
|
||||
## 会话缓存
|
||||
|
||||
在分布式场景下具有多个应用服务器,可以使用 Redis 来统一存储这些应用服务器的会话信息。
|
||||
可以使用 Redis 来统一存储多台应用服务器的会话信息。
|
||||
|
||||
当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* [I/O 复用](#io-复用)
|
||||
* [信号驱动 I/O](#信号驱动-io)
|
||||
* [异步 I/O](#异步-io)
|
||||
* [同步 I/O 与异步 I/O](#同步-io-与异步-io)
|
||||
* [五大 I/O 模型比较](#五大-io-模型比较)
|
||||
* [二、I/O 复用](#二io-复用)
|
||||
* [select](#select)
|
||||
|
@ -39,7 +38,7 @@ Unix 有五种 I/O 模型:
|
|||
|
||||
应用进程被阻塞,直到数据复制到应用进程缓冲区中才返回。
|
||||
|
||||
应该注意到,在阻塞的过程中,其它程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,因此不消耗 CPU 时间,这种模型的 CPU 利用率效率会比较高。
|
||||
应该注意到,在阻塞的过程中,其它程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率效率会比较高。
|
||||
|
||||
下图中,recvfrom 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。
|
||||
|
||||
|
@ -63,7 +62,7 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
|
|||
|
||||
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。
|
||||
|
||||
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。并且相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
|
||||
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
|
||||
|
||||
<div align="center"> <img src="../pics//1492929444818_6.png"/> </div><br>
|
||||
|
||||
|
@ -83,16 +82,14 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
|
|||
|
||||
<div align="center"> <img src="../pics//1492930243286_8.png"/> </div><br>
|
||||
|
||||
## 同步 I/O 与异步 I/O
|
||||
|
||||
- 同步 I/O:应用进程在调用 recvfrom 操作时会阻塞。
|
||||
- 异步 I/O:不会阻塞。
|
||||
|
||||
阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞。
|
||||
|
||||
## 五大 I/O 模型比较
|
||||
|
||||
前四种 I/O 模型的主要区别在于第一个阶段,而第二个阶段是一样的:将数据从内核复制到应用进程过程中,应用进程会被阻塞。
|
||||
- 同步 I/O:将数据从内核缓冲区复制到应用进程缓冲区的阶段,应用进程会阻塞。
|
||||
- 异步 I/O:不会阻塞。
|
||||
|
||||
阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,它们的主要区别在第一个阶段。
|
||||
|
||||
非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
|
||||
|
||||
<div align="center"> <img src="../pics//1492928105791_3.png"/> </div><br>
|
||||
|
||||
|
@ -181,12 +178,12 @@ else if ( ret == 0 )
|
|||
else
|
||||
{
|
||||
// If we detect the event, zero it out so we can reuse the structure
|
||||
if ( pfd[0].revents & POLLIN )
|
||||
pfd[0].revents = 0;
|
||||
if ( fds[0].revents & POLLIN )
|
||||
fds[0].revents = 0;
|
||||
// input event on sock1
|
||||
|
||||
if ( pfd[1].revents & POLLOUT )
|
||||
pfd[1].revents = 0;
|
||||
if ( fds[1].revents & POLLOUT )
|
||||
fds[1].revents = 0;
|
||||
// output event on sock2
|
||||
}
|
||||
```
|
||||
|
@ -299,7 +296,7 @@ epoll 的描述符事件有两种触发模式:LT(level trigger)和 ET(ed
|
|||
|
||||
### 1. select 应用场景
|
||||
|
||||
select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时要求更高的场景,比如核反应堆的控制。
|
||||
select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。
|
||||
|
||||
select 可移植性更好,几乎被所有主流平台所支持。
|
||||
|
||||
|
@ -307,13 +304,13 @@ select 可移植性更好,几乎被所有主流平台所支持。
|
|||
|
||||
poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
|
||||
|
||||
需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。
|
||||
|
||||
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且epoll 的描述符存储在内核,不容易调试。
|
||||
|
||||
### 3. epoll 应用场景
|
||||
|
||||
只需要运行在 Linux 平台上,并且有非常大量的描述符需要同时轮询,而且这些连接最好是长连接。
|
||||
只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。
|
||||
|
||||
需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。
|
||||
|
||||
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。
|
||||
|
||||
# 参考资料
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
|
||||
- [Twitter Java Style Guide](https://github.com/twitter/commons/blob/master/src/java/com/twitter/common/styleguide.md)
|
||||
- [Google Java Style Guide](http://google.github.io/styleguide/javaguide.html)
|
||||
- [阿里巴巴Java开发手册](https://github.com/alibaba/p3c/blob/master/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E8%AF%A6%E5%B0%BD%E7%89%88%EF%BC%89.pdf)
|
||||
|
|
34
notes/分布式.md
|
@ -99,7 +99,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
|
|||
|
||||
# 二、分布式事务
|
||||
|
||||
指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
|
||||
指事务的操作位于不同的节点上,需要保证事务的 ACID 特性。
|
||||
|
||||
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
|
||||
|
||||
|
@ -141,7 +141,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
|
|||
|
||||
#### 2.2 单点问题
|
||||
|
||||
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
|
||||
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待,无法完成其它操作。
|
||||
|
||||
#### 2.3 数据不一致
|
||||
|
||||
|
@ -167,7 +167,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
|
|||
|
||||
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
|
||||
|
||||
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操,请求总是能够在有限的时间内返回结果。
|
||||
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
|
||||
|
||||
## 分区容忍性
|
||||
|
||||
|
@ -181,8 +181,8 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
|
|||
|
||||
可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
|
||||
|
||||
- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成;
|
||||
- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。
|
||||
- 为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性;
|
||||
- 为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致。
|
||||
|
||||
<div align="center"> <img src="../pics//0b587744-c0a8-46f2-8d72-e8f070d67b4b.jpg"/> </div><br>
|
||||
|
||||
|
@ -228,31 +228,37 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE
|
|||
|
||||
规定一个提议包含两个字段:[n, v],其中 n 为序号(具有唯一性),v 为提议值。
|
||||
|
||||
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送提议请求。
|
||||
### 1. Prepare 阶段
|
||||
|
||||
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送 Prepare 请求。
|
||||
|
||||
<div align="center"> <img src="../pics//1a9977e4-2f5c-49a6-aec9-f3027c9f46a7.png"/> </div><br>
|
||||
|
||||
当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
|
||||
当 Acceptor 接收到一个 Prepare 请求,包含的提议为 [n1, v1],并且之前还未接收过 Prepare 请求,那么发送一个 Prepare 响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
|
||||
|
||||
如下图,Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。
|
||||
如下图,Acceptor X 在收到 [n=2, v=8] 的 Prepare 请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的 Prepare 响应,设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。
|
||||
|
||||
<div align="center"> <img src="../pics//fb44307f-8e98-4ff7-a918-31dacfa564b4.jpg"/> </div><br>
|
||||
|
||||
如果 Acceptor 接收到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2,那么就丢弃该提议请求;否则,发送提议响应,该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。
|
||||
如果 Acceptor 接收到一个 Prepare 请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2,那么就丢弃该提议请求;否则,发送 Prepare 响应,该 Prepare 响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。
|
||||
|
||||
如下图,Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2,因此就抛弃该提议请求;Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,因为之前接收到的提议为 [n=2, v=8],并且 2 <= 4,因此就发送 [n=2, v=8] 的提议响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。
|
||||
如下图,Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的 Prepare 请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2,因此就抛弃该提议请求;Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的 Prepare 请求,因为之前接收到的提议为 [n=2, v=8],并且 2 <= 4,因此就发送 [n=2, v=8] 的 Prepare 响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。
|
||||
|
||||
<div align="center"> <img src="../pics//2bcc58ad-bf7f-485c-89b5-e7cafc211ce2.jpg"/> </div><br>
|
||||
|
||||
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。
|
||||
### 2. Accept 阶段
|
||||
|
||||
Proposer A 接收到两个提议响应之后,就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
|
||||
当一个 Proposer 接收到超过一半 Acceptor 的 Prepare 响应时,就可以发送 Accept 请求。
|
||||
|
||||
Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。
|
||||
Proposer A 接收到两个 Prepare 响应之后,就发送 [n=2, v=8] Accept 请求。该 Accept 请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
|
||||
|
||||
Proposer B 过后也收到了两个 Prepare 响应,因此也开始发送 Accept 请求。需要注意的是,Accept 请求的 v 需要取它收到的最大提议编号对应的 v 值,也就是 8。因此它发送 [n=4, v=8] 的 Accept 请求。
|
||||
|
||||
<div align="center"> <img src="../pics//9b838aee-0996-44a5-9b0f-3d1e3e2f5100.png"/> </div><br>
|
||||
|
||||
Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
|
||||
### 3. Learn 阶段
|
||||
|
||||
Acceptor 接收到 Accept 请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送 Learn 提议给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
|
||||
|
||||
<div align="center"> <img src="../pics//bf667594-bb4b-4634-bf9b-0596a45415ba.jpg"/> </div><br>
|
||||
|
||||
|
|
|
@ -223,14 +223,14 @@ Output:
|
|||
|
||||
在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20),因此当遍历到一个空格时,需要在尾部填充两个任意字符。
|
||||
|
||||
令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。
|
||||
令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。
|
||||
|
||||
从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。
|
||||
|
||||
```java
|
||||
public String replaceSpace(StringBuffer str) {
|
||||
int P1 = str.length() - 1;
|
||||
for (int i = 0; i < P1 + 1; i++)
|
||||
for (int i = 0; i <= P1; i++)
|
||||
if (str.charAt(i) == ' ')
|
||||
str.append(" ");
|
||||
|
||||
|
@ -385,6 +385,7 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
|
|||
|
||||
```java
|
||||
public class TreeLinkNode {
|
||||
|
||||
int val;
|
||||
TreeLinkNode left = null;
|
||||
TreeLinkNode right = null;
|
||||
|
@ -510,6 +511,7 @@ public int Fibonacci(int n) {
|
|||
|
||||
```java
|
||||
public class Solution {
|
||||
|
||||
private int[] fib = new int[40];
|
||||
|
||||
public Solution() {
|
||||
|
@ -722,7 +724,7 @@ private char[][] buildMatrix(char[] array) {
|
|||
|
||||
地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。
|
||||
|
||||
例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,37),因为 3+5+3+8=19。请问该机器人能够达到多少个格子?
|
||||
例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子?
|
||||
|
||||
## 解题思路
|
||||
|
||||
|
@ -956,7 +958,7 @@ private void printNumber(char[] number) {
|
|||
|
||||
```java
|
||||
public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
|
||||
if (head == null || head.next == null || tobeDelete == null)
|
||||
if (head == null || tobeDelete == null)
|
||||
return null;
|
||||
if (tobeDelete.next != null) {
|
||||
// 要删除的节点不是尾节点
|
||||
|
@ -1154,11 +1156,11 @@ public ListNode FindKthToTail(ListNode head, int k) {
|
|||
|
||||
## 解题思路
|
||||
|
||||
使用双指针,一个指针 fast 每次移动两个节点,一个指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。假设相遇点在下图的 z1 位置,此时 fast 移动的节点数为 x+2y+z,slow 为 x+y,由于 fast 速度比 slow 快一倍,因此 x+2y+z=2(x+y),得到 x=z。
|
||||
使用双指针,一个指针 fast 每次移动两个节点,一个指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。假设相遇点在下图的 y6 位置,此时 fast 移动的节点数为 x+2y+z,slow 为 x+y,由于 fast 速度比 slow 快一倍,因此 x+2y+z=2(x+y),得到 x=z。
|
||||
|
||||
在相遇点,slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点。在上面已经推导出 x=z,因此 fast 和 slow 将在环入口点相遇。
|
||||
|
||||
<div align="center"> <img src="../pics//2858f8ad-aedb-45a5-a706-e98c96d690fa.jpg" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//70fa1f83-dae7-456d-b94b-ce28963b2ba1.png" width="500"/> </div><br>
|
||||
|
||||
```java
|
||||
public ListNode EntryNodeOfLoop(ListNode pHead) {
|
||||
|
@ -1433,7 +1435,8 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
|
|||
Stack<Integer> stack = new Stack<>();
|
||||
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
|
||||
stack.push(pushSequence[pushIndex]);
|
||||
while (popIndex < n && stack.peek() == popSequence[popIndex]) {
|
||||
while (popIndex < n && !stack.isEmpty()
|
||||
&& stack.peek() == popSequence[popIndex]) {
|
||||
stack.pop();
|
||||
popIndex++;
|
||||
}
|
||||
|
@ -1558,7 +1561,7 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
|
|||
|
||||
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。
|
||||
|
||||
例如,下图是后序遍历序列 3,1,2 所对应的二叉搜索树。
|
||||
例如,下图是后序遍历序列 1,3,2 所对应的二叉搜索树。
|
||||
|
||||
<div align="center"> <img src="../pics//836a4eaf-4798-4e48-b52a-a3dab9435ace.png" width="150"/> </div><br>
|
||||
|
||||
|
@ -1578,7 +1581,7 @@ private boolean verify(int[] sequence, int first, int last) {
|
|||
int cutIndex = first;
|
||||
while (cutIndex < last && sequence[cutIndex] <= rootVal)
|
||||
cutIndex++;
|
||||
for (int i = cutIndex + 1; i < last; i++)
|
||||
for (int i = cutIndex; i < last; i++)
|
||||
if (sequence[i] < rootVal)
|
||||
return false;
|
||||
return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
|
||||
|
@ -2687,8 +2690,8 @@ public ArrayList<Integer> maxInWindows(int[] num, int size) {
|
|||
for (int i = 0; i < size; i++)
|
||||
heap.add(num[i]);
|
||||
ret.add(heap.peek());
|
||||
for (int i = 1, j = i + size - 1; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */
|
||||
heap.remove(num[i - 1]);
|
||||
for (int i = 0, j = i + size; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */
|
||||
heap.remove(num[i]);
|
||||
heap.add(num[j]);
|
||||
ret.add(heap.peek());
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
跨站脚本攻击(Cross-Site Scripting, XSS),可以将代码注入到用户浏览的网页上,这种代码包括 HTML 和 JavaScript。
|
||||
|
||||
## 攻击原理
|
||||
|
||||
例如有一个论坛网站,攻击者可以在上面发布以下内容:
|
||||
|
||||
```html
|
||||
|
@ -43,40 +45,23 @@
|
|||
|
||||
例如将 `<` 转义为 `<`,将 `>` 转义为 `>`,从而避免 HTML 和 Jascript 代码的运行。
|
||||
|
||||
## 富文本编辑器
|
||||
|
||||
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
|
||||
|
||||
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,通过定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
|
||||
|
||||
以下例子中,form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
|
||||
|
||||
> [XSS 过滤在线测试](http://jsxss.com/zh/try.html)
|
||||
|
||||
```html
|
||||
<h1 id="title">XSS Demo</h1>
|
||||
|
||||
<p class="text-center">
|
||||
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
|
||||
</p>
|
||||
<p>123</p>
|
||||
|
||||
<form>
|
||||
<input type="text" name="q" value="test">
|
||||
<button id="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<pre>hello</pre>
|
||||
|
||||
<p>
|
||||
<a href="http://jsxss.com">http</a>
|
||||
</p>
|
||||
|
||||
<h3>Features:</h3>
|
||||
<ul>
|
||||
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
|
||||
<li>Handle any tags or attributes using custom function</li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
alert(/xss/);
|
||||
</script>
|
||||
|
@ -85,32 +70,21 @@ alert(/xss/);
|
|||
```html
|
||||
<h1>XSS Demo</h1>
|
||||
|
||||
<p>
|
||||
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
|
||||
</p>
|
||||
<p>123</p>
|
||||
|
||||
<form>
|
||||
<input type="text" name="q" value="test">
|
||||
<button id="submit">Submit</button>
|
||||
</form>
|
||||
|
||||
<pre>hello</pre>
|
||||
|
||||
<p>
|
||||
<a href="http://jsxss.com">http</a>
|
||||
</p>
|
||||
|
||||
<h3>Features:</h3>
|
||||
<ul>
|
||||
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
|
||||
<li>Handle any tags or attributes using custom function</li>
|
||||
</ul>
|
||||
|
||||
<script type="text/javascript">
|
||||
alert(/xss/);
|
||||
</script>
|
||||
```
|
||||
|
||||
> [XSS 过滤在线测试](http://jsxss.com/zh/try.html)
|
||||
|
||||
# 二、跨站请求伪造
|
||||
|
||||
## 概念
|
||||
|
@ -119,6 +93,8 @@ alert(/xss/);
|
|||
|
||||
XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户浏览器的信任。
|
||||
|
||||
## 攻击原理
|
||||
|
||||
假如一家银行用以执行转账操作的 URL 地址如下:
|
||||
|
||||
```
|
||||
|
@ -135,7 +111,7 @@ http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
|
|||
|
||||
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
|
||||
|
||||
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
|
||||
通过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
|
||||
|
||||
## 防范手段
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
|
||||
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
|
||||
|
||||
回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
|
||||
回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
|
||||
|
||||
### 2. 一致性(Consistency)
|
||||
|
||||
|
@ -69,7 +69,7 @@
|
|||
|
||||
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
|
||||
|
||||
可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复。
|
||||
使用重做日志来保证持久性。
|
||||
|
||||
----
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
|||
- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
|
||||
- 事务满足持久化是为了能应对数据库崩溃的情况。
|
||||
|
||||
<div align="center"> <img src="../pics//a58e294a-615d-4ea0-9fbf-064a6daec4b2.png" width="450"/> </div><br>
|
||||
<div align="center"> <img src="../pics//6675d713-8b59-4067-ad16-fdd538d4bb43.png" width="500"/> </div><br>
|
||||
|
||||
## AUTOCOMMIT
|
||||
|
||||
|
@ -221,8 +221,8 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
|
|||
| unlock-x(A) |. |
|
||||
| | obtain |
|
||||
| | read A=20 |
|
||||
| | commit |
|
||||
| | unlock-s(A)|
|
||||
| | commit |
|
||||
|
||||
**三级封锁协议**
|
||||
|
||||
|
@ -294,12 +294,12 @@ SELECT ... FOR UPDATE;
|
|||
|
||||
----
|
||||
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
|
||||
| :---: | :---: | :---:| :---: |
|
||||
| 未提交读 | √ | √ | √ |
|
||||
| 提交读 | × | √ | √ |
|
||||
| 可重复读 | × | × | √ |
|
||||
| 可串行化 | × | × | × |
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | 加锁读 |
|
||||
| :---: | :---: | :---:| :---: | :---: |
|
||||
| 未提交读 | √ | √ | √ | × |
|
||||
| 提交读 | × | √ | √ | × |
|
||||
| 可重复读 | × | × | √ | × |
|
||||
| 可串行化 | × | × | × | √ |
|
||||
|
||||
# 五、多版本并发控制
|
||||
|
||||
|
|
|
@ -96,7 +96,8 @@ POM 代表项目对象模型,它是一个 XML 文件,保存在项目根目
|
|||
</dependency>
|
||||
```
|
||||
|
||||
[groupId, artifactId, version, packaging, classfier] 称为一个项目的坐标,其中 groupId、artifactId、version 必须定义,packaging 可选(默认为 Jar),classfier 不能直接定义的,需要结合插件使用。
|
||||
[groupId, artifactId, version, packaging, classifier] 称为一个项目的坐标,其中 groupId、artifactId、version 必须定义,packaging 可选(默认为 Jar),classifier 不能直接定义的,需要结合插件使用。
|
||||
|
||||
|
||||
- groupId:项目组 Id,必须全球唯一;
|
||||
- artifactId:项目 Id,即项目名;
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
|
||||
接收端能够从消息队列成功消费一次消息。
|
||||
|
||||
实现方法:
|
||||
两种实现方法:
|
||||
|
||||
- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
|
||||
- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
|
||||
|
|
16
notes/算法.md
|
@ -301,7 +301,7 @@ public class Selection<T extends Comparable<T>> extends Sort<T> {
|
|||
@Override
|
||||
public void sort(T[] nums) {
|
||||
int N = nums.length;
|
||||
for (int i = 0; i < N; i++) {
|
||||
for (int i = 0; i < N - 1; i++) {
|
||||
int min = i;
|
||||
for (int j = i + 1; j < N; j++) {
|
||||
if (less(nums[j], nums[min])) {
|
||||
|
@ -459,7 +459,7 @@ public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> {
|
|||
|
||||
将一个大数组分成两个小数组去求解。
|
||||
|
||||
因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)。
|
||||
因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN)。
|
||||
|
||||
```java
|
||||
public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {
|
||||
|
@ -541,7 +541,7 @@ public class QuickSort<T extends Comparable<T>> extends Sort<T> {
|
|||
|
||||
### 2. 切分
|
||||
|
||||
取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。
|
||||
取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。
|
||||
|
||||
<div align="center"> <img src="../pics//766aedd0-1b00-4065-aa2b-7d31138df84f.png" width="400"/> </div><br>
|
||||
|
||||
|
@ -617,7 +617,7 @@ public class ThreeWayQuickSort<T extends Comparable<T>> extends QuickSort<T> {
|
|||
|
||||
可以利用这个特性找出数组的第 k 个元素。
|
||||
|
||||
该算法是线性级别的,因为每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。
|
||||
该算法是线性级别的,假设每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。
|
||||
|
||||
```java
|
||||
public T select(T[] nums, int k) {
|
||||
|
@ -694,7 +694,7 @@ private void swim(int k) {
|
|||
}
|
||||
```
|
||||
|
||||
类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那么节点进行交换。
|
||||
类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那个节点进行交换。
|
||||
|
||||
<div align="center"> <img src="../pics//72f0ff69-138d-4e54-b7ac-ebe025d978dc.png" width="400"/> </div><br>
|
||||
|
||||
|
@ -796,7 +796,7 @@ public class HeapSort<T extends Comparable<T>> extends Sort<T> {
|
|||
|
||||
对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlogN。
|
||||
|
||||
堆排序时一种原地排序,没有利用额外的空间。
|
||||
堆排序是一种原地排序,没有利用额外的空间。
|
||||
|
||||
现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较。
|
||||
|
||||
|
@ -1123,7 +1123,7 @@ public class ArrayStack<Item> implements MyStack<Item> {
|
|||
|
||||
### 2. 链表实现
|
||||
|
||||
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素时就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素称为新的栈顶元素。
|
||||
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素时就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素成为新的栈顶元素。
|
||||
|
||||
```java
|
||||
public class ListStack<Item> implements MyStack<Item> {
|
||||
|
@ -2292,7 +2292,7 @@ from H1 to H3
|
|||
|
||||
可以将每种字符转换成二进制编码,例如将 a 转换为 00,b 转换为 01,c 转换为 10,d 转换为 11。这是最简单的一种编码方式,没有考虑各个字符的权值(出现频率)。而哈夫曼编码采用了贪心策略,使出现频率最高的字符的编码最短,从而保证整体的编码长度最短。
|
||||
|
||||
首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点在树的最底层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。
|
||||
首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点位于树的更低层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。
|
||||
|
||||
生成编码时,从根节点出发,向左遍历则添加二进制位 0,向右则添加二进制位 1,直到遍历到叶子节点,叶子节点代表的字符的编码就是这个路径编码。
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* [三、扩展性](#三扩展性)
|
||||
* [四、可用性](#四可用性)
|
||||
* [五、安全性](#五安全性)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
|
@ -101,4 +102,8 @@
|
|||
|
||||
# 五、安全性
|
||||
|
||||
要求系统的应对各种攻击手段时能够有可靠的应对措施。
|
||||
要求系统在应对各种攻击手段时能够有可靠的应对措施。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- 大型网站技术架构:核心原理与案例分析
|
||||
|
|
39
notes/缓存.md
|
@ -58,6 +58,7 @@ public class LRU<K, V> implements Iterable<K> {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public LRU(int maxSize) {
|
||||
|
||||
this.maxSize = maxSize;
|
||||
|
@ -70,6 +71,7 @@ public class LRU<K, V> implements Iterable<K> {
|
|||
tail.pre = head;
|
||||
}
|
||||
|
||||
|
||||
public V get(K key) {
|
||||
|
||||
if (!map.containsKey(key)) {
|
||||
|
@ -83,6 +85,7 @@ public class LRU<K, V> implements Iterable<K> {
|
|||
return node.v;
|
||||
}
|
||||
|
||||
|
||||
public void put(K key, V value) {
|
||||
|
||||
if (map.containsKey(key)) {
|
||||
|
@ -96,32 +99,52 @@ public class LRU<K, V> implements Iterable<K> {
|
|||
|
||||
if (map.size() > maxSize) {
|
||||
Node toRemove = removeTail();
|
||||
map.remove(toRemove);
|
||||
map.remove(toRemove.k);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void unlink(Node node) {
|
||||
|
||||
Node pre = node.pre;
|
||||
node.pre = node.next;
|
||||
node.next = pre;
|
||||
Node next = node.next;
|
||||
|
||||
pre.next = next;
|
||||
next.pre = pre;
|
||||
|
||||
node.pre = null;
|
||||
node.next = null;
|
||||
}
|
||||
|
||||
|
||||
private void appendHead(Node node) {
|
||||
node.next = head.next;
|
||||
Node next = head.next;
|
||||
node.next = next;
|
||||
next.pre = node;
|
||||
node.pre = head;
|
||||
head.next = node;
|
||||
}
|
||||
|
||||
|
||||
private Node removeTail() {
|
||||
|
||||
Node node = tail.pre;
|
||||
node.pre = tail;
|
||||
|
||||
Node pre = node.pre;
|
||||
tail.pre = pre;
|
||||
pre.next = tail;
|
||||
|
||||
node.pre = null;
|
||||
node.next = null;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Iterator<K> iterator() {
|
||||
|
||||
return new Iterator<K>() {
|
||||
|
||||
private Node cur = head.next;
|
||||
|
||||
@Override
|
||||
|
@ -162,7 +185,7 @@ public class LRU<K, V> implements Iterable<K> {
|
|||
|
||||
使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。
|
||||
|
||||
相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存,而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。
|
||||
相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存,而本地缓存需要在服务器集群之间进行同步,实现难度和性能开销上都非常大。
|
||||
|
||||
## 数据库缓存
|
||||
|
||||
|
@ -241,7 +264,7 @@ Distributed Hash Table(DHT) 是一种哈希分布方式,其目的是为了
|
|||
|
||||
<div align="center"> <img src="../pics//68b110b9-76c6-4ee2-b541-4145e65adb3e.jpg"/> </div><br>
|
||||
|
||||
一致性哈希在增加或者删除节点时只会影响到哈希环中相邻的节点,例如下图中新增节点 X,只需要将它后一个节点 C 上的数据重新进行分布即可,对于节点 A、B、D 都没有影响。
|
||||
一致性哈希在增加或者删除节点时只会影响到哈希环中相邻的节点,例如下图中新增节点 X,只需要将它前一个节点 C 上的数据重新进行分布即可,对于节点 A、B、D 都没有影响。
|
||||
|
||||
<div align="center"> <img src="../pics//66402828-fb2b-418f-83f6-82153491bcfe.jpg"/> </div><br>
|
||||
|
||||
|
|
146
notes/计算机操作系统.md
|
@ -13,8 +13,8 @@
|
|||
* [经典同步问题](#经典同步问题)
|
||||
* [进程通信](#进程通信)
|
||||
* [三、死锁](#三死锁)
|
||||
* [死锁的必要条件](#死锁的必要条件)
|
||||
* [死锁的处理方法](#死锁的处理方法)
|
||||
* [必要条件](#必要条件)
|
||||
* [处理方法](#处理方法)
|
||||
* [鸵鸟策略](#鸵鸟策略)
|
||||
* [死锁检测与死锁恢复](#死锁检测与死锁恢复)
|
||||
* [死锁预防](#死锁预防)
|
||||
|
@ -173,7 +173,7 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
|
|||
|
||||
Ⅱ 调度
|
||||
|
||||
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。
|
||||
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
|
||||
|
||||
Ⅲ 系统开销
|
||||
|
||||
|
@ -181,7 +181,7 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
|
|||
|
||||
Ⅳ 通信方面
|
||||
|
||||
进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。
|
||||
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
|
||||
|
||||
## 进程状态的切换
|
||||
|
||||
|
@ -243,7 +243,7 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
|
|||
|
||||
**2.3 多级反馈队列**
|
||||
|
||||
如果一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。
|
||||
一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。
|
||||
|
||||
多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
|
||||
|
||||
|
@ -338,9 +338,9 @@ void consumer() {
|
|||
down(&full);
|
||||
down(&mutex);
|
||||
int item = remove_item();
|
||||
consume_item(item);
|
||||
up(&mutex);
|
||||
up(&empty);
|
||||
consume_item(item);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -458,6 +458,116 @@ void writer() {
|
|||
}
|
||||
```
|
||||
|
||||
以下内容由 [@Bandi Yugandhar](https://github.com/yugandharbandi) 提供。
|
||||
|
||||
The first case may result Writer to starve. This case favous Writers i.e no writer, once added to the queue, shall be kept waiting longer than absolutely necessary(only when there are readers that entered the queue before the writer).
|
||||
|
||||
```source-c
|
||||
int readcount, writecount; //(initial value = 0)
|
||||
semaphore rmutex, wmutex, readLock, resource; //(initial value = 1)
|
||||
|
||||
//READER
|
||||
void reader() {
|
||||
<ENTRY Section>
|
||||
down(&readLock); // reader is trying to enter
|
||||
down(&rmutex); // lock to increase readcount
|
||||
readcount++;
|
||||
if (readcount == 1)
|
||||
down(&resource); //if you are the first reader then lock the resource
|
||||
up(&rmutex); //release for other readers
|
||||
up(&readLock); //Done with trying to access the resource
|
||||
|
||||
<CRITICAL Section>
|
||||
//reading is performed
|
||||
|
||||
<EXIT Section>
|
||||
down(&rmutex); //reserve exit section - avoids race condition with readers
|
||||
readcount--; //indicate you're leaving
|
||||
if (readcount == 0) //checks if you are last reader leaving
|
||||
up(&resource); //if last, you must release the locked resource
|
||||
up(&rmutex); //release exit section for other readers
|
||||
}
|
||||
|
||||
//WRITER
|
||||
void writer() {
|
||||
<ENTRY Section>
|
||||
down(&wmutex); //reserve entry section for writers - avoids race conditions
|
||||
writecount++; //report yourself as a writer entering
|
||||
if (writecount == 1) //checks if you're first writer
|
||||
down(&readLock); //if you're first, then you must lock the readers out. Prevent them from trying to enter CS
|
||||
up(&wmutex); //release entry section
|
||||
|
||||
<CRITICAL Section>
|
||||
down(&resource); //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource
|
||||
//writing is performed
|
||||
up(&resource); //release file
|
||||
|
||||
<EXIT Section>
|
||||
down(&wmutex); //reserve exit section
|
||||
writecount--; //indicate you're leaving
|
||||
if (writecount == 0) //checks if you're the last writer
|
||||
up(&readLock); //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading
|
||||
up(&wmutex); //release exit section
|
||||
}
|
||||
```
|
||||
|
||||
We can observe that every reader is forced to acquire ReadLock. On the otherhand, writers doesn’t need to lock individually. Once the first writer locks the ReadLock, it will be released only when there is no writer left in the queue.
|
||||
|
||||
From the both cases we observed that either reader or writer has to starve. Below solutionadds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time.
|
||||
|
||||
```source-c
|
||||
int readCount; // init to 0; number of readers currently accessing resource
|
||||
|
||||
// all semaphores initialised to 1
|
||||
Semaphore resourceAccess; // controls access (read/write) to the resource
|
||||
Semaphore readCountAccess; // for syncing changes to shared variable readCount
|
||||
Semaphore serviceQueue; // FAIRNESS: preserves ordering of requests (signaling must be FIFO)
|
||||
|
||||
void writer()
|
||||
{
|
||||
down(&serviceQueue); // wait in line to be servicexs
|
||||
// <ENTER>
|
||||
down(&resourceAccess); // request exclusive access to resource
|
||||
// </ENTER>
|
||||
up(&serviceQueue); // let next in line be serviced
|
||||
|
||||
// <WRITE>
|
||||
writeResource(); // writing is performed
|
||||
// </WRITE>
|
||||
|
||||
// <EXIT>
|
||||
up(&resourceAccess); // release resource access for next reader/writer
|
||||
// </EXIT>
|
||||
}
|
||||
|
||||
void reader()
|
||||
{
|
||||
down(&serviceQueue); // wait in line to be serviced
|
||||
down(&readCountAccess); // request exclusive access to readCount
|
||||
// <ENTER>
|
||||
if (readCount == 0) // if there are no readers already reading:
|
||||
down(&resourceAccess); // request resource access for readers (writers blocked)
|
||||
readCount++; // update count of active readers
|
||||
// </ENTER>
|
||||
up(&serviceQueue); // let next in line be serviced
|
||||
up(&readCountAccess); // release access to readCount
|
||||
|
||||
// <READ>
|
||||
readResource(); // reading is performed
|
||||
// </READ>
|
||||
|
||||
down(&readCountAccess); // request exclusive access to readCount
|
||||
// <EXIT>
|
||||
readCount--; // update count of active readers
|
||||
if (readCount == 0) // if there are no readers left:
|
||||
up(&resourceAccess); // release resource access for all
|
||||
// </EXIT>
|
||||
up(&readCountAccess); // release access to readCount
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
### 2. 哲学家进餐问题
|
||||
|
||||
<div align="center"> <img src="../pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br>
|
||||
|
@ -503,7 +613,7 @@ void philosopher(int i) {
|
|||
think();
|
||||
take_two(i);
|
||||
eat();
|
||||
put_tow(i);
|
||||
put_two(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -515,7 +625,7 @@ void take_two(int i) {
|
|||
down(&s[i]);
|
||||
}
|
||||
|
||||
void put_tow(i) {
|
||||
void put_two(i) {
|
||||
down(&mutex);
|
||||
state[i] = THINKING;
|
||||
test(LEFT);
|
||||
|
@ -596,7 +706,7 @@ FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户
|
|||
|
||||
# 三、死锁
|
||||
|
||||
## 死锁的必要条件
|
||||
## 必要条件
|
||||
|
||||
<div align="center"> <img src="../pics//c037c901-7eae-4e31-a1e4-9d41329e5c3e.png"/> </div><br>
|
||||
|
||||
|
@ -605,11 +715,11 @@ FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户
|
|||
- 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
|
||||
- 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
|
||||
|
||||
## 死锁的处理方法
|
||||
## 处理方法
|
||||
|
||||
主要有以下四种方法:
|
||||
|
||||
- 鸵鸟策略;
|
||||
- 鸵鸟策略
|
||||
- 死锁检测与死锁恢复
|
||||
- 死锁预防
|
||||
- 死锁避免
|
||||
|
@ -727,7 +837,7 @@ FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户
|
|||
|
||||
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
|
||||
|
||||
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序称为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
|
||||
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序成为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
|
||||
|
||||
<div align="center"> <img src="../pics//7b281b1e-0595-402b-ae35-8c91084c33c1.png"/> </div><br>
|
||||
|
||||
|
@ -735,9 +845,9 @@ FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户
|
|||
|
||||
内存管理单元(MMU)管理着地址空间和物理内存的转换,其中的页表(Page table)存储着页(程序地址空间)和页框(物理内存空间)的映射表。
|
||||
|
||||
下图的页表存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位,也就是存储页面号,剩下 12 个比特位存储偏移量。
|
||||
一个虚拟地址分成两个部分,一部分存储页面号,一部分存储偏移量。
|
||||
|
||||
例如对于虚拟地址(0010 000000000100),前 4 位是存储页面号 2,读取表项内容为(110 1)。该页在内存中,并且页框的地址为 (110 000000000100)。
|
||||
下图的页表存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位。例如对于虚拟地址(0010 000000000100),前 4 位是存储页面号 2,读取表项内容为(110 1),页表项最后一位表示是否存在于内存中,1 表示存在。后 12 位存储偏移量。这个页对应的页框的地址为 (110 000000000100)。
|
||||
|
||||
<div align="center"> <img src="../pics//cf4386a1-58c9-4eca-a17f-e12b1e9770eb.png" width="500"/> </div><br>
|
||||
|
||||
|
@ -751,7 +861,7 @@ FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户
|
|||
|
||||
### 1. 最佳
|
||||
|
||||
> Optimal
|
||||
> OPT, Optimal replacement algorithm
|
||||
|
||||
所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。
|
||||
|
||||
|
@ -769,7 +879,7 @@ FIFO 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户
|
|||
|
||||
虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
|
||||
|
||||
为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面时最近最久未访问的。
|
||||
为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。
|
||||
|
||||
因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
|
||||
|
||||
|
@ -879,7 +989,7 @@ FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问
|
|||
|
||||
优先调度与当前磁头所在磁道距离最近的磁道。
|
||||
|
||||
虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两边的磁道请求更容易出现饥饿现象。
|
||||
虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两端的磁道请求更容易出现饥饿现象。
|
||||
|
||||
<div align="center"> <img src="../pics//4e2485e4-34bd-4967-9f02-0c093b797aaa.png"/> </div><br>
|
||||
|
||||
|
@ -928,7 +1038,7 @@ gcc -o hello hello.c
|
|||
|
||||
## 静态链接
|
||||
|
||||
静态连接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
|
||||
静态链接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
|
||||
|
||||
- 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
|
||||
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* [主机之间的通信方式](#主机之间的通信方式)
|
||||
* [电路交换与分组交换](#电路交换与分组交换)
|
||||
* [时延](#时延)
|
||||
* [计算机网络体系结构*](#计算机网络体系结构)
|
||||
* [计算机网络体系结构](#计算机网络体系结构)
|
||||
* [二、物理层](#二物理层)
|
||||
* [通信方式](#通信方式)
|
||||
* [带通调制](#带通调制)
|
||||
|
@ -13,14 +13,14 @@
|
|||
* [基本问题](#基本问题)
|
||||
* [信道分类](#信道分类)
|
||||
* [信道复用技术](#信道复用技术)
|
||||
* [CSMA/CD 协议*](#csmacd-协议)
|
||||
* [CSMA/CD 协议](#csmacd-协议)
|
||||
* [PPP 协议](#ppp-协议)
|
||||
* [MAC 地址](#mac-地址)
|
||||
* [局域网](#局域网)
|
||||
* [以太网*](#以太网)
|
||||
* [交换机*](#交换机)
|
||||
* [以太网](#以太网)
|
||||
* [交换机](#交换机)
|
||||
* [虚拟局域网](#虚拟局域网)
|
||||
* [四、网络层*](#四网络层)
|
||||
* [四、网络层](#四网络层)
|
||||
* [概述](#概述)
|
||||
* [IP 数据报格式](#ip-数据报格式)
|
||||
* [IP 地址编址方式](#ip-地址编址方式)
|
||||
|
@ -31,7 +31,7 @@
|
|||
* [路由器的结构](#路由器的结构)
|
||||
* [路由器分组转发流程](#路由器分组转发流程)
|
||||
* [路由选择协议](#路由选择协议)
|
||||
* [五、运输层*](#五运输层)
|
||||
* [五、传输层](#五传输层)
|
||||
* [UDP 和 TCP 的特点](#udp-和-tcp-的特点)
|
||||
* [UDP 首部格式](#udp-首部格式)
|
||||
* [TCP 首部格式](#tcp-首部格式)
|
||||
|
@ -121,7 +121,7 @@
|
|||
|
||||
分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。
|
||||
|
||||
## 计算机网络体系结构*
|
||||
## 计算机网络体系结构
|
||||
|
||||
<div align="center"> <img src="../pics//426df589-6f97-4622-b74d-4a81fcb1da8e.png" width="600"/> </div><br>
|
||||
|
||||
|
@ -129,11 +129,11 @@
|
|||
|
||||
- **应用层** :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。
|
||||
|
||||
- **运输层** :提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。
|
||||
- **传输层** :为进程提供通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。
|
||||
|
||||
- **网络层** :为主机间提供数据传输服务,而运输层协议是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。
|
||||
- **网络层** :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。
|
||||
|
||||
- **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供服务。数据链路层把网络层传下来的分组封装成帧。
|
||||
- **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。
|
||||
|
||||
- **物理层** :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
|
||||
|
||||
|
@ -155,7 +155,7 @@ TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接
|
|||
|
||||
<div align="center"> <img src="../pics//45e0e0bf-386d-4280-a341-a0b9496c7674.png" width="400"/> </div><br>
|
||||
|
||||
TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中占用举足轻重的地位。
|
||||
TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中占据举足轻重的地位。
|
||||
|
||||
<div align="center"> <img src="../pics//d4eef1e2-5703-4ca4-82ab-8dda93d6b81f.png" width="500"/> </div><br>
|
||||
|
||||
|
@ -163,7 +163,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
|
|||
|
||||
在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。
|
||||
|
||||
路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要运输层和应用层。
|
||||
路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要传输层和应用层。
|
||||
|
||||
<div align="center"> <img src="../pics//ac106e7e-489a-4082-abd9-dabebe48394c.jpg" width="800"/> </div><br>
|
||||
|
||||
|
@ -271,7 +271,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
|
|||
|
||||
<div align="center"> <img src="../pics//92ad9bae-7d02-43ba-8115-a9d6f530ca28.png" width="600"/> </div><br>
|
||||
|
||||
## CSMA/CD 协议*
|
||||
## CSMA/CD 协议
|
||||
|
||||
CSMA/CD 表示载波监听多点接入 / 碰撞检测。
|
||||
|
||||
|
@ -316,11 +316,11 @@ MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标
|
|||
|
||||
<div align="center"> <img src="../pics//a6026bb4-3daf-439f-b1ec-a5a24e19d2fb.jpg" width="600"/> </div><br>
|
||||
|
||||
## 以太网*
|
||||
## 以太网
|
||||
|
||||
以太网是一种星型拓扑结构局域网。
|
||||
|
||||
早期使用集线器进行连接,集线器是一种物理层设备, 作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到同时从两个不同接口的帧,那么就发生了碰撞。
|
||||
早期使用集线器进行连接,集线器是一种物理层设备, 作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到两个不同接口的帧,那么就发生了碰撞。
|
||||
|
||||
目前以太网使用交换机替代了集线器,交换机是一种链路层设备,它不会发生碰撞,能根据 MAC 地址进行存储转发。
|
||||
|
||||
|
@ -333,7 +333,7 @@ MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标
|
|||
|
||||
<div align="center"> <img src="../pics//50d38e84-238f-4081-8876-14ef6d7938b5.jpg" width="600"/> </div><br>
|
||||
|
||||
## 交换机*
|
||||
## 交换机
|
||||
|
||||
交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。
|
||||
|
||||
|
@ -353,7 +353,7 @@ MAC 地址是链路层地址,长度为 6 字节(48 位),用于唯一标
|
|||
|
||||
<div align="center"> <img src="../pics//a74b70ac-323a-4b31-b4d5-90569b8a944b.png" width="500"/> </div><br>
|
||||
|
||||
# 四、网络层*
|
||||
# 四、网络层
|
||||
|
||||
## 概述
|
||||
|
||||
|
@ -498,7 +498,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
|
|||
|
||||
专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。
|
||||
|
||||
在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把运输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。
|
||||
在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把传输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。
|
||||
|
||||
<div align="center"> <img src="../pics//2719067e-b299-4639-9065-bed6729dbf0b.png" width=""/> </div><br>
|
||||
|
||||
|
@ -578,9 +578,9 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。
|
|||
|
||||
<div align="center"> <img src="../pics//9cd0ae20-4fb5-4017-a000-f7d3a0eb3529.png" width="600"/> </div><br>
|
||||
|
||||
# 五、运输层*
|
||||
# 五、传输层
|
||||
|
||||
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。运输层提供了进程间的逻辑通信,运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个运输层实体之间有一条端到端的逻辑通信信道。
|
||||
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个传输层实体之间有一条端到端的逻辑通信信道。
|
||||
|
||||
## UDP 和 TCP 的特点
|
||||
|
||||
|
@ -808,7 +808,7 @@ IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删
|
|||
|
||||
## 常用端口
|
||||
|
||||
|应用| 应用层协议 | 端口号 | 运输层协议 | 备注 |
|
||||
|应用| 应用层协议 | 端口号 | 传输层协议 | 备注 |
|
||||
| :---: | :--: | :--: | :--: | :--:
|
||||
| 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP |
|
||||
| 动态主机配置协议 | DHCP | 67/68 | UDP | |
|
||||
|
|
106
notes/设计模式.md
|
@ -95,7 +95,7 @@ private static Singleton uniqueInstance = new Singleton();
|
|||
|
||||
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance。
|
||||
|
||||
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过程,因此该方法有性能问题,不推荐使用。
|
||||
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。
|
||||
|
||||
```java
|
||||
public static synchronized Singleton getUniqueInstance() {
|
||||
|
@ -143,13 +143,13 @@ if (uniqueInstance == null) {
|
|||
}
|
||||
```
|
||||
|
||||
uniqueInstance 采用 volatile 关键字修饰也是很有必要的。`uniqueInstance = new Singleton();` 这段代码其实是分为三步执行。
|
||||
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, `uniqueInstance = new Singleton();` 这段代码其实是分为三步执行:
|
||||
|
||||
1. 为 uniqueInstance 分配内存空间
|
||||
2. 初始化 uniqueInstance
|
||||
3. 将 uniqueInstance 指向分配的内存地址
|
||||
|
||||
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T<sub>1</sub> 执行了 1 和 3,此时 T<sub>2</sub> 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
|
||||
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T<sub>1</sub> 执行了 1 和 3,此时 T<sub>2</sub> 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。
|
||||
|
||||
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
|
||||
|
||||
|
@ -175,9 +175,60 @@ public class Singleton {
|
|||
}
|
||||
```
|
||||
|
||||
该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现,为了保证不会出现反序列化之后出现多个实例,需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。
|
||||
#### Ⅵ 枚举实现
|
||||
|
||||
该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止实例化第二个对象的代码。但是该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。
|
||||
```java
|
||||
public enum Singleton {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private String objName;
|
||||
|
||||
|
||||
public String getObjName() {
|
||||
return objName;
|
||||
}
|
||||
|
||||
|
||||
public void setObjName(String objName) {
|
||||
this.objName = objName;
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
// 单例测试
|
||||
Singleton firstSingleton = Singleton.INSTANCE;
|
||||
firstSingleton.setObjName("firstName");
|
||||
System.out.println(firstSingleton.getObjName());
|
||||
Singleton secondSingleton = Singleton.INSTANCE;
|
||||
secondSingleton.setObjName("secondName");
|
||||
System.out.println(firstSingleton.getObjName());
|
||||
System.out.println(secondSingleton.getObjName());
|
||||
|
||||
// 反射获取实例测试
|
||||
try {
|
||||
Singleton[] enumConstants = Singleton.class.getEnumConstants();
|
||||
for (Singleton enumConstant : enumConstants) {
|
||||
System.out.println(enumConstant.getObjName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
firstName
|
||||
secondName
|
||||
secondName
|
||||
secondName
|
||||
```
|
||||
|
||||
该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。
|
||||
|
||||
该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。
|
||||
|
||||
### Examples
|
||||
|
||||
|
@ -200,7 +251,7 @@ public class Singleton {
|
|||
|
||||
### Class Diagram
|
||||
|
||||
简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
|
||||
简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
|
||||
|
||||
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
|
||||
|
||||
|
@ -232,6 +283,7 @@ public class ConcreteProduct2 implements Product {
|
|||
|
||||
```java
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args) {
|
||||
int type = 1;
|
||||
Product product;
|
||||
|
@ -251,6 +303,7 @@ public class Client {
|
|||
|
||||
```java
|
||||
public class SimpleFactory {
|
||||
|
||||
public Product createProduct(int type) {
|
||||
if (type == 1) {
|
||||
return new ConcreteProduct1();
|
||||
|
@ -264,6 +317,7 @@ public class SimpleFactory {
|
|||
|
||||
```java
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SimpleFactory simpleFactory = new SimpleFactory();
|
||||
Product product = simpleFactory.createProduct(1);
|
||||
|
@ -595,25 +649,30 @@ abc
|
|||
|
||||
```java
|
||||
public abstract class Handler {
|
||||
|
||||
protected Handler successor;
|
||||
|
||||
|
||||
public Handler(Handler successor) {
|
||||
this.successor = successor;
|
||||
}
|
||||
|
||||
|
||||
protected abstract void handleRequest(Request request);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class ConcreteHandler1 extends Handler {
|
||||
|
||||
public ConcreteHandler1(Handler successor) {
|
||||
super(successor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleRequest(Request request) {
|
||||
if (request.getType() == RequestType.type1) {
|
||||
if (request.getType() == RequestType.TYPE1) {
|
||||
System.out.println(request.getName() + " is handle by ConcreteHandler1");
|
||||
return;
|
||||
}
|
||||
|
@ -625,14 +684,16 @@ public class ConcreteHandler1 extends Handler {
|
|||
```
|
||||
|
||||
```java
|
||||
public class ConcreteHandler2 extends Handler{
|
||||
public class ConcreteHandler2 extends Handler {
|
||||
|
||||
public ConcreteHandler2(Handler successor) {
|
||||
super(successor);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleRequest(Request request) {
|
||||
if (request.getType() == RequestType.type2) {
|
||||
if (request.getType() == RequestType.TYPE2) {
|
||||
System.out.println(request.getName() + " is handle by ConcreteHandler2");
|
||||
return;
|
||||
}
|
||||
|
@ -645,38 +706,47 @@ public class ConcreteHandler2 extends Handler{
|
|||
|
||||
```java
|
||||
public class Request {
|
||||
|
||||
private RequestType type;
|
||||
private String name;
|
||||
|
||||
|
||||
public Request(RequestType type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
||||
public RequestType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```java
|
||||
public enum RequestType {
|
||||
type1, type2
|
||||
TYPE1, TYPE2
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class Client {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Handler handler1 = new ConcreteHandler1(null);
|
||||
Handler handler2 = new ConcreteHandler2(handler1);
|
||||
Request request1 = new Request(RequestType.type1, "request1");
|
||||
|
||||
Request request1 = new Request(RequestType.TYPE1, "request1");
|
||||
handler2.handleRequest(request1);
|
||||
Request request2 = new Request(RequestType.type2, "request2");
|
||||
|
||||
Request request2 = new Request(RequestType.TYPE2, "request2");
|
||||
handler2.handleRequest(request2);
|
||||
}
|
||||
}
|
||||
|
@ -2282,7 +2352,7 @@ public class Client {
|
|||
|
||||
### Class Diagram
|
||||
|
||||
<div align="center"> <img src="../pics//0f754c1d-b5cb-48cd-90e0-4a86034290a1.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//0889c0b4-07b4-45fc-873c-e0e16b97f67d.png"/> </div><br>
|
||||
|
||||
### Implementation
|
||||
|
||||
|
@ -2487,6 +2557,10 @@ public class Client {
|
|||
remoteControl1.on();
|
||||
remoteControl1.off();
|
||||
remoteControl1.tuneChannel();
|
||||
RemoteControl remoteControl2 = new ConcreteRemoteControl2(new Sony());
|
||||
remoteControl2.on();
|
||||
remoteControl2.off();
|
||||
remoteControl2.tuneChannel();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -2508,7 +2582,7 @@ public class Client {
|
|||
|
||||
组合对象拥有一个或者多个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象。
|
||||
|
||||
<div align="center"> <img src="../pics//3fb5b255-b791-45b6-8754-325c8741855a.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//77931a4b-72ba-4016-827d-84b9a6845a51.png"/> </div><br>
|
||||
|
||||
### Implementation
|
||||
|
||||
|
@ -2892,7 +2966,7 @@ Java 利用缓存来加速大量小对象的访问时间。
|
|||
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
|
||||
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
|
||||
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
|
||||
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
|
||||
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
|
||||
|
||||
<div align="center"> <img src="../pics//a6c20f60-5eba-427d-9413-352ada4b40fe.png"/> </div><br>
|
||||
|
||||
|
@ -2944,6 +3018,7 @@ public class HighResolutionImage implements Image {
|
|||
|
||||
```java
|
||||
public class ImageProxy implements Image {
|
||||
|
||||
private HighResolutionImage highResolutionImage;
|
||||
|
||||
public ImageProxy(HighResolutionImage highResolutionImage) {
|
||||
|
@ -2967,6 +3042,7 @@ public class ImageProxy implements Image {
|
|||
|
||||
```java
|
||||
public class ImageViewer {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String image = "http://image.jpg";
|
||||
URL url = new URL(image);
|
||||
|
|
12
notes/集群.md
|
@ -18,12 +18,12 @@
|
|||
负载均衡器可以用来实现高可用以及伸缩性:
|
||||
|
||||
- 高可用:当某个节点故障时,负载均衡器会将用户请求转发到另外的节点上,从而保证所有服务持续可用;
|
||||
- 伸缩性:可以很容易地添加移除节点。
|
||||
- 伸缩性:根据系统整体负载情况,可以很容易地添加移除节点。
|
||||
|
||||
负载均衡运行过程包含两个部分:
|
||||
|
||||
1. 根据负载均衡算法得到请求转发的节点;
|
||||
2. 将请求进行转发。
|
||||
1. 根据负载均衡算法得到转发的节点;
|
||||
2. 进行转发。
|
||||
|
||||
## 负载均衡算法
|
||||
|
||||
|
@ -116,11 +116,6 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
|
|||
|
||||
### 3. 反向代理服务器
|
||||
|
||||
首先了解一下正向代理与反向代理的区别:
|
||||
|
||||
- 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端;
|
||||
- 反向代理:发生在服务器端,用户不知道反向代理的存在。
|
||||
|
||||
反向代理服务器位于源服务器前面,用户的请求需要先经过反向代理服务器才能到达源服务器。反向代理可以用来进行缓存、日志记录等,同时也可以用来做为负载均衡服务器。
|
||||
|
||||
在这种负载均衡转发方式下,客户端不直接请求源服务器,因此源服务器不需要外部 IP 地址,而反向代理需要配置内部和外部两套 IP 地址。
|
||||
|
@ -205,4 +200,3 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
|
|||
|
||||
- [Session Management using Spring Session with JDBC DataStore](https://sivalabs.in/2018/02/session-management-using-spring-session-jdbc-datastore/)
|
||||
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ Vihical <|-- Trunck
|
|||
|
||||
## 实现关系 (Realization)
|
||||
|
||||
用来实现一个接口,在 Java 中使用 implement 关键字。
|
||||
用来实现一个接口,在 Java 中使用 implements 关键字。
|
||||
|
||||
<div align="center"> <img src="../pics//SoWkIImgAStDuU8goIp9ILK8IatCoQn.png"/> </div><br>
|
||||
|
||||
|
|
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 9.4 KiB |
|
@ -2,8 +2,6 @@
|
|||
|
||||
这个交流群不是一个笔者的问题回答群,我更希望大家能够愿意积极回答,我相信提问和回答的过程都可以提高大家对知识的掌握程度。
|
||||
|
||||
因为笔者白天要上班,因此不能及时进行回复,大部分时间会处于潜水状态。
|
||||
|
||||
至于交流群和 Issue 有什么区别,主要是两方面:一是交流群实时性高一些,二是交流群会更活跃一些。
|
||||
|
||||
另外,Issue 主要是用来发布一些项目中的错误和一些改进建议,当然也可以发布一些可以讨论的问题。
|
||||
|
@ -13,3 +11,5 @@
|
|||
交流群不讨论政治,不讨论有争议性的话题,不发表仇视言论,不传播谣言,不发布广告(招聘信息之类的可以)。
|
||||
|
||||
</br> <div align="center"><img src="group.png" width="300px"></div> </br>
|
||||
|
||||
|
||||
|
|
151
other/算法与数据结构.md
Normal file
|
@ -0,0 +1,151 @@
|
|||
# Algorithm
|
||||
leetcode/lintcode上的算法题
|
||||
|
||||
**关于问题的答案和解体的思路,可以移步 : https://github.com/zhaozhengcoder/Algorithm**
|
||||
|
||||
### About
|
||||
|
||||
这个仓库最初的想法是把lintcode/lintocde上面的算法题目整理一下,因为很多题目太多了显得太乱了,就不继续在GitHub上面写了,以前写的一部分移到我的博客上面了。
|
||||
GitHub上面打算整理一些比较典型 或者是 自己思考过的觉得很好的问题。
|
||||
|
||||
|
||||
在博客上面开了两个专栏
|
||||
|
||||
1. 数据结构/算法导论 :
|
||||
https://www.jianshu.com/nb/12397278
|
||||
|
||||
2. OJ练习题 :
|
||||
https://www.jianshu.com/nb/9973135
|
||||
|
||||
推荐两篇自己对 递归搜索和动态规划 的理解的blog :
|
||||
|
||||
1. https://www.jianshu.com/p/5eb4da919efe
|
||||
|
||||
2. https://www.jianshu.com/p/6b3a2304f63f
|
||||
|
||||
|
||||
|
||||
### 题目的索引
|
||||
GITHUB上面打算整理一些比较典型 或者是 自己思考过的觉得很好的问题。
|
||||
|
||||
1.从数据结构的角度索引 :
|
||||
|
||||
a. 数组
|
||||
|
||||
两数之和
|
||||
|
||||
连续最大子数组
|
||||
|
||||
乘积最大子数组
|
||||
|
||||
买卖股票的最佳时机1,2,3
|
||||
|
||||
买卖股票的最佳时机1:寻找数组里面的最大上升子序列
|
||||
买卖股票的最佳时机2:寻找数组里面所有的上升子序列
|
||||
买卖股票的最佳时机3:寻找数组里面两个不重合的上升子序列,并且使他们的和最大 to-do
|
||||
|
||||
区间合并(将有交集的区间合并)
|
||||
|
||||
寻找缺失的数
|
||||
|
||||
1. 一个顺序的数组[1,2,3,5,6],缺少了一个数字,如何找到它?
|
||||
|
||||
2. 一个arr的数组,只有一个数字出现了一次,其他都出现了两次,如何找到它?
|
||||
|
||||
数组的近似划分(将一个数组分成两个,但是差最小)
|
||||
|
||||
数组里面第k大的数
|
||||
|
||||
跳跃游戏1,2
|
||||
|
||||
跳跃游戏1:
|
||||
给出一个非负整数数组,你最初定位在数组的第一个位置,
|
||||
数组中的每个元素代表你在那个位置可以跳跃的最大长度,
|
||||
返回 是否能到达数组的最后一个位置
|
||||
|
||||
跳跃游戏2:
|
||||
给出一个非负整数数组,你最初定位在数组的第一个位置,
|
||||
数组中的每个元素代表你在那个位置可以跳跃的最大长度,
|
||||
返回 使用最少的跳跃次数到达数组的最后一个位置
|
||||
|
||||
a+. 二维矩阵
|
||||
|
||||
顺时针打印二维矩阵
|
||||
|
||||
给出一个二维矩阵,找到一个路径(从某个左上角到某个角右下)使这条路径的值最大
|
||||
|
||||
b. 链表
|
||||
|
||||
c. 字符串
|
||||
|
||||
最长公共子序列(并不是连续的)
|
||||
|
||||
最长回文子串
|
||||
|
||||
d. 二叉树
|
||||
|
||||
返回一个平衡二叉树的第k大的节点
|
||||
|
||||
二叉树的最低公共祖先
|
||||
|
||||
非递归遍历二叉树
|
||||
|
||||
e. 图
|
||||
|
||||
最短路径
|
||||
|
||||
深度/广度优先遍历
|
||||
|
||||
2. 从算法的角度建立索引 :
|
||||
|
||||
a. 递归搜索问题
|
||||
|
||||
N后问题
|
||||
|
||||
全排列
|
||||
|
||||
组合问题1,2
|
||||
|
||||
b. 动态规划
|
||||
|
||||
背包问题1,2
|
||||
|
||||
数组的近似划分(将一个数组分成两个,但是差最小)
|
||||
|
||||
跳跃游戏1,2
|
||||
|
||||
给出一个二维矩阵,找到一个路径(从某个左上角到某个角右下)使这条路径的值最大
|
||||
|
||||
|
||||
3. 常用
|
||||
|
||||
a. 排列/组合
|
||||
|
||||
b. 深度优先遍历
|
||||
|
||||
c. 最短路径
|
||||
|
||||
4. 智力题(算法本身很简单,就是想不到的那种)
|
||||
|
||||
最多有多少个点在同一条直线上
|
||||
|
||||
|
||||
### Others
|
||||
|
||||
1. 类似于系统设计的题目
|
||||
|
||||
带最小值的栈/队列
|
||||
|
||||
url长链接转短链接
|
||||
|
||||
2. 解决特定问题
|
||||
|
||||
并查集
|
||||
|
||||
布隆过滤器
|
||||
|
||||
|
||||
|
||||
如果你对机器学习的算法感兴趣,欢迎共同讨论:
|
||||
|
||||
https://github.com/zhaozhengcoder/Machine-Learning
|
BIN
pics/0889c0b4-07b4-45fc-873c-e0e16b97f67d.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
pics/12f958ee-ea02-481a-86ae-acc24934ad80.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
pics/28ab96b4-82ea-4d99-99fb-b320f60d0a58.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
pics/6675d713-8b59-4067-ad16-fdd538d4bb43.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
pics/70fa1f83-dae7-456d-b94b-ce28963b2ba1.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
pics/77931a4b-72ba-4016-827d-84b9a6845a51.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
pics/85370d54-40d1-4912-bcbe-37a2481c861d.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
pics/VP6n3i8W48Ptde8NQ9_0eSR5eOD6uqx.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
pics/f8b3f73d-0fda-449f-b55b-fa36b7ac04cd.png
Normal file
After Width: | Height: | Size: 16 KiB |