diff --git a/DevelopCommonTools/GitIntroduce.md b/DevelopCommonTools/GitIntroduce.md new file mode 100644 index 00000000000..e58f13b63aa --- /dev/null +++ b/DevelopCommonTools/GitIntroduce.md @@ -0,0 +1,258 @@ + + +- [版本控制](#版本控制) + - [什么是版本控制](#什么是版本控制) + - [为什么要版本控制](#为什么要版本控制) + - [本地版本控制系统](#本地版本控制系统) + - [集中化的版本控制系统](#集中化的版本控制系统) + - [分布式版本控制系统](#分布式版本控制系统) +- [认识 Git](#认识-git) + - [Git 简史](#git-简史) + - [Git 与其他版本管理系统的主要区别](#git-与其他版本管理系统的主要区别) + - [Git 的三种状态](#git-的三种状态) +- [Git 使用快速入门](#git-使用快速入门) + - [获取 Git 仓库](#获取-git-仓库) + - [记录每次更新到仓库](#记录每次更新到仓库) + - [推送改动到远程仓库](#推送改动到远程仓库) + - [远程仓库的移除与重命名](#远程仓库的移除与重命名) + - [查看提交历史](#查看提交历史) + - [撤销操作](#撤销操作) + - [分支](#分支) +- [推荐阅读](#推荐阅读) + + + +## 版本控制 + +### 什么是版本控制 + +版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。 除了项目源代码,你可以对任何类型的文件进行版本控制。 + +### 为什么要版本控制 + +有了它你就可以将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。 + +### 本地版本控制系统 + +许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。 这么做唯一的好处就是简单,但是特别容易犯错。 有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。 + +为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。 + +![本地版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/本地版本控制系统.png) + +### 集中化的版本控制系统 + +接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作? 于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 + +集中化的版本控制系统都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 + +![集中化的版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/集中化的版本控制系统.png) + +这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病,但也还是存在下面的问题: + +- **单点故障:** 中央服务器宕机,则其他人无法使用;如果中心数据库磁盘损坏有没有进行备份,你将丢失所有数据。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。 +- **必须联网才能工作:** 受网络状况、带宽影响。 + +### 分布式版本控制系统 + +于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 Git 就是一个典型的分布式版本控制系统。 + +这类系统,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。 + +![分布式版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/分布式版本控制系统.png) + +分布式版本控制系统可以不用联网就可以工作,因为每个人的电脑上都是完整的版本库,当你修改了某个文件后,你只需要将自己的修改推送给别人就可以了。但是,在实际使用分布式版本控制系统的时候,很少会直接进行推送修改,而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。 + +分布式版本控制系统的优势不单是不必联网这么简单,后面我们还会看到 Git 极其强大的分支管理等功能。 + +## 认识 Git + +### Git 简史 + +Linux 内核项目组当时使用分布式版本控制系统 BitKeeper 来管理和维护代码。但是,后来开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统,而且对新的版本控制系统做了很多改进。 + +### Git 与其他版本管理系统的主要区别 + + Git 在保存和对待各种信息的时候与其它版本控制系统有很大差异,尽管操作起来的命令形式非常相近,理解这些差异将有助于防止你使用中的困惑。 + +下面我们主要说一个关于 Git 其他版本管理系统的主要差别:**对待数据的方式**。 + +**Git采用的是直接记录快照的方式,而非差异比较。我后面会详细介绍这两种方式的差别。** + +大部分版本控制系统(CVS、Subversion、Perforce、Bazaar 等等)都是以文件变更列表的方式存储信息,这类系统**将它们保存的信息看作是一组基本文件和每个文件随时间逐步累积的差异。** + +具体原理如下图所示,理解起来其实很简单,每个我们对提交更新一个文件之后,系统记录都会记录这个文件做了哪些更新,以增量符号Δ(Delta)表示。 + +
+ +
+
+ +**我们怎样才能得到一个文件的最终版本呢?** + +很简单,高中数学的基本知识,我们只需要将这些原文件和这些增加进行相加就行了。 + +**这种方式有什么问题呢?** + +比如我们的增量特别特别多的话,如果我们要得到最终的文件是不是会耗费时间和性能。 + +Git 不按照以上方式对待或保存数据。 反之,Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 **快照流**。 + +
+ +
+
+ + +### Git 的三种状态 + +Git 有三种状态,你的文件可能处于其中之一: + +1. **已提交(committed)**:数据已经安全的保存在本地数据库中。 +2. **已修改(modified)**:已修改表示修改了文件,但还没保存到数据库中。 +3. **已暂存(staged)**:表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。 + +由此引入 Git 项目的三个工作区域的概念:**Git 仓库(.git directoty) **、**工作目录(Working Directory)** 以及 **暂存区域(Staging Area)** 。 + +
+ +
+ +**基本的 Git 工作流程如下:** + +1. 在工作目录中修改文件。 +2. 暂存文件,将文件的快照放入暂存区域。 +3. 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。 + +## Git 使用快速入门 + +### 获取 Git 仓库 + +有两种取得 Git 项目仓库的方法。 + +1. 在现有目录中初始化仓库: 进入项目目录运行 `git init` 命令,该命令将创建一个名为 `.git` 的子目录。 +2. 从一个服务器克隆一个现有的 Git 仓库: `git clone [url]` 自定义本地仓库的名字: `git clone [url]` directoryname + +### 记录每次更新到仓库 + +1. **检测当前文件状态** : `git status` +2. **提出更改(把它们添加到暂存区**):`git add filename ` (针对特定文件)、`git add *`(所有文件)、`git add *.txt`(支持通配符,所有 .txt 文件) +3. **忽略文件**:`.gitignore` 文件 +4. **提交更新:** `git commit -m "代码提交信息"` (每次准备提交前,先用 `git status` 看下,是不是都已暂存起来了, 然后再运行提交命令 `git commit`) +5. **跳过使用暂存区域更新的方式** : `git commit -a -m "代码提交信息"`。 `git commit` 加上 `-a` 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 `git add` 步骤。 +6. **移除文件** :`git rm filename` (从暂存区域移除,然后提交。) +7. **对文件重命名** :`git mv README.md README`(这个命令相当于`mv README.md README`、`git rm README.md`、`git add README` 这三条命令的集合) + +### 推送改动到远程仓库 + +- 如果你还没有克隆现有仓库,并欲将你的仓库连接到某个远程服务器,你可以使用如下命令添加:·`git remote add origin ` ,比如我们要让本地的一个仓库和 Github 上创建的一个仓库关联可以这样`git remote add origin https://github.com/Snailclimb/test.git` +- 将这些改动提交到远端仓库:`git push origin master` (可以把 *master* 换成你想要推送的任何分支) + + 如此你就能够将你的改动推送到所添加的服务器上去了。 + +### 远程仓库的移除与重命名 + +- 将 test 重命名位 test1:`git remote rename test test1` +- 移除远程仓库 test1:`git remote rm test1` + +### 查看提交历史 + +在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的工具是 `git log` 命令。`git log` 会按提交时间列出所有的更新,最近的更新排在最上面。 + +**可以添加一些参数来查看自己希望看到的内容:** + +只看某个人的提交记录: + +```shell +git log --author=bob +``` + +### 撤销操作 + +有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 `--amend` 选项的提交命令尝试重新提交: + +```console +git commit --amend +``` + +取消暂存的文件 + +```console +git reset filename +``` + +撤消对文件的修改: + +``` +git checkout -- filename +``` + +假如你想丢弃你在本地的所有改动与提交,可以到服务器上获取最新的版本历史,并将你本地主分支指向它: + +``` +git fetch origin +git reset --hard origin/master +``` + + + +### 分支 + +分支是用来将特性开发绝缘开来的。在你创建仓库的时候,*master* 是“默认的”分支。在其他分支上进行开发,完成后再将它们合并到主分支上。 + +我们通常在开发新功能、修复一个紧急 bug 等等时候会选择创建分支。单分支开发好还是多分支开发好,还是要看具体场景来说。 + +创建一个名字叫做 test 的分支 + +```console +git branch test +``` + +切换当前分支到 test(当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样) + +```console +git checkout test +``` + +
+ +
+ +你也可以直接这样创建分支并切换过去(上面两条命令的合写) + +```console +git checkout -b feature_x +``` + +切换到主分支 + +``` +git checkout master +``` + +合并分支(可能会有冲突) + +```java + git merge test +``` + +把新建的分支删掉 + +``` +git branch -d feature_x +``` + +将分支推送到远端仓库(推送成功后其他人可见): + +``` +git push origin +``` + + + +## 推荐阅读 + +- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html) +- [图解Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html) +- [猴子都能懂得Git入门](https://backlog.com/git-tutorial/cn/intro/intro1_1.html) +- https://git-scm.com/book/en/v2 diff --git "a/EssentialContentForInterview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" "b/EssentialContentForInterview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" new file mode 100644 index 00000000000..9efac14f6d4 --- /dev/null +++ "b/EssentialContentForInterview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" @@ -0,0 +1,96 @@ +> 作者:ppxyn。本文来自读者投稿,同时也欢迎各位投稿,**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。 + +**目录** + + + +- [前言](#前言) +- [一面\(技术面\)](#一面技术面) +- [二面\(技术面\)](#二面技术面) +- [三面\(技术面\)](#三面技术面) +- [四面\(半个技术面\)](#四面半个技术面) +- [五面\(HR面\)](#五面hr面) +- [总结](#总结) + + + +### 前言 + +在接触 Java 之前我接触的比较多的是硬件方面,用的比较多的语言就是C和C++。到了大三我才正式选择 Java 方向,到目前为止使用Java到现在大概有一年多的时间,所以Java算不上很好。刚开始投递的时候,实习刚辞职,也没准备笔试面试,很多东西都忘记了。所以,刚开始我并没有直接就投递阿里,毕竟心里还是有一点点小害怕的。于是,我就先投递了几个不算大的公司来练手,就是想着刷刷经验而已或者说是练练手(ps:还是挺对不起那些公司的)。面了一个月其他公司后,我找了我实验室的学长内推我,后面就有了这5次面试。 + +下面简单的说一下我的这5次面试:4次技术面+1次HR面,希望我的经历能对你有所帮助。 + +### 一面(技术面) + +1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。 +2. 聊聊项目(就是一个很普通的分布式商城,自己做了一些改进),让我画了整个项目的架构图,然后针对项目抛了一系列的提高性能的问题,还问了我做项目的过程中遇到了那些问题,如何解决的,差不读就这些吧。 +3. 可能是我前面说了我会数据库优化,然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题。 +4. 浏览器输入URL发生了什么? TCP和UDP区别? TCP如何保证传输可靠性? +5. 讲下跳表怎么实现的?哈夫曼编码是怎么回事?非递归且不用额外空间(不用栈),如何遍历二叉树 +6. 后面又问了很多JVM方面的问题,比如Java内存模型、常见的垃圾回收器、双亲委派模型这些 +7. 你有什么问题要问吗? + +### 二面(技术面) + +1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。 +2. 操作系统的内存管理机制 +3. 进程和线程的区别 +4. 说下你对线程安全的理解 +5. volatile 有什么作用 ,sychronized和lock有什么区别 +6. ReentrantLock实现原理 +7. 用过CountDownLatch么?什么场景下用的? +8. AQS底层原理。 +9. 造成死锁的原因有哪些,如何预防? +10. 加锁会带来哪些性能问题。如何解决? +11. HashMap、ConcurrentHashMap源码。HashMap是线程安全的吗?Hashtable呢?ConcurrentHashMap有了解吗? +12. 是否可以实习? +13. 你有什么问题要问吗? + +### 三面(技术面) + +1. 有没有参加过 ACM 或者他竞赛,有没有拿过什么奖?( 我说我没参加过ACM,本科参加过数学建模竞赛,名次并不好,没拿过什么奖。面试官好像有点失望,然后我又赶紧补充说我和老师一起做过一个项目,目前已经投入使用。面试官还比较感兴趣,后面又和他聊了一下这个项目。) +2. 研究生期间,做过什么项目,发过论文吗?有什么成果吗? +3. 你觉得你有什么优点和缺点?你觉得你相比于那些比你更优秀的人欠缺什么? +4. 有读过什么源码吗?(我说我读过 Java 集合框架和 Netty 的,面试官说 Java 集合前几面一定问的差不多,就不问了,然后就问我 Netty的,我当时很慌啊!) +5. 介绍一下自己对 Netty 的认识,为什么要用。说说业务中,Netty 的使用场景。什么是TCP 粘包/拆包,解决办法。Netty线程模型。Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题?讲讲Netty的零拷贝?巴拉巴拉问了好多,我记得有好几个我都没回答上来,心里想着凉凉了啊。 +6. 用到了那些开源技术、在开源领域做过贡献吗? +7. 常见的排序算法及其复杂度,现场写了快排。 +8. 红黑树,B树的一些问题。 +9. 讲讲算法及数据结构在实习项目中的用处。 +10. 自己的未来规划(就简单描述了一下自己未来的设想啊,说的还挺诚恳,面试官好像还挺满意的) +11. 你有什么问题要问吗? + +### 四面(半个技术面) + +三面面完当天,晚上9点接到面试电话,感觉像是部门或者项目主管。 这个和之前的面试不大相同,感觉面试官主要考察的是你解决问题的能力、学习能力和团队协作能力。 + +1. 让我讲一个自己觉得最不错的项目。然后就巴拉巴拉的聊,我记得主要是问了项目是如何进行协作的、遇到问题是如何解决的、与他人发生冲突是如何解决的这些。感觉聊了挺久。 +2. 出现 OOM 后你会怎么排查问题? +3. 自己平时是如何学习新技术的?除了 Java 还回去了解其他技术吗? +4. 上一段实习经历的收获。 +5. NginX如何做负载均衡、常见的负载均衡算法有哪些、一致性哈希的一致性是什么意思、一致性哈希是如何做哈希的 +6. 你有什么问题问我吗? +7. 还有一些其他的,想不起来了,感觉这一面不是偏向技术来问。 + +## 五面(HR面) + +1. 自我介绍(主要讲能突出自己的经历,会的编程技术一语带过)。 +2. 你觉得你有什么优点和缺点?如何克服这些缺点? +3. 说一件大学里你自己比较有成就感的一件事情,为此付出了那些努力。 +4. 你前面跟其他面试官讲过一些你做的项目吧?可以给我讲讲吗?你要考虑到我不是一个做技术的人,怎么让我也听得懂。项目中有什么问题,你怎么解决的?你最大的收获是什么? +5. 你目前有面试过其他公司吗?如果让你选,这些公司和阿里,你选哪个?(送分题,回答不好可能送命) +6. 你期望的工作地点是哪里? +7. 你有什么问题吗? + +### 总结 + +1. 可以看出面试官问我的很多问题都是比较常见的问题,所以记得一定要提前准备,还要深入准备,不要回答的太皮毛。很多时候一个问题可能会牵扯出很多问题,遇到不会的问题不要慌,冷静分析,如果你真的回答不上来,也不要担心自己是不是就要挂了,很可能这个问题本身就比较难。 +2. 表达能力和沟通能力太重要了,一定要提前练一下,我自身就是一个不太会说话的人,所以,面试前我对于自我介绍、项目介绍和一些常见问题都在脑子里练了好久,确保面试的时候能够很清晰和简洁的说出来。 +3. 等待面试的过程和面试的过程真的好熬人,那段时间我压力也比较大,好在我私下找到学长聊了很多,心情也好了很多。 +4. 面试之后及时总结,面的好的话,不要得意,尽快准备下一场面试吧! + +我觉得我还算是比较幸运的,最后也祝大家都能获得心仪的Offer。 + + + + diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" "b/EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" similarity index 55% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" rename to "EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" index 6214a716cb8..4ca58dbfff6 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" +++ "b/EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\344\270\200\345\221\250\357\274\2102018-8-7\357\274\211.md" @@ -1,20 +1,16 @@ -## 一 Java中的值传递和引用传递(非常重要) +## 一 为什么 Java 中只有值传递? -**首先要明确的是:“对象传递(数组、类、接口)是引用传递,原始类型数据(整型、浮点型、字符型、布尔型)传递是值传递。”** -### 那么什么是值传递和应用传递呢? +首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。 -**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。) +**Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。** -**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。) +**下面通过 3 个例子来给大家说明** -有时候面试官不是单纯问你“Java中是值传递还是引用传递”是什么啊,骚年?而是给出一个例子,然后让你写出答案,这种也常见在笔试题目中!所以,非常重要了,请看下面的例子: +### example 1 -### 值传递和应用传递实例 - -#### 1. 值传递 ```java public static void main(String[] args) { @@ -48,157 +44,116 @@ num2 = 20 **解析:** -在swap方法中,a、b的值进行交换,并不会影响到num1、num2。因为,a、b中的值,只是从num1、num2的复制过来的。 -也就是说,a、b相当于num1、num2的副本,副本的内容无论怎么修改,都不会影响到原件本身。 - -#### 2. 引用传递 - -```java -public static void main(String[] args) { - int[] arr = {1,2,3,4,5}; - - change(arr); - - System.out.println(arr[0]); -} - -public static void change(int[] array) { -//将数组的第一个元素变为0 - array[0] = 0; -} -``` - -**结果:** - -``` -1 -0 -``` +![example 1 ](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/22191348.jpg) -**解析:** +在swap方法中,a、b的值进行交换,并不会影响到 num1、num2。因为,a、b中的值,只是从 num1、num2 的复制过来的。也就是说,a、b相当于num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。 -无论是主函数,还是change方法,操作的都是同一个地址值对应的数组。 。因此,外部对引用对象的改变会反映到所有的对象上。 +**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.** -### 一些特殊的例子 -#### 1. StringBuffer类型传递 +### example 2 ```java - // 测试引用传递:StringBuffer - @org.junit.Test - public void method1() { - StringBuffer str = new StringBuffer("公众号:Java面试通关手册"); - System.out.println(str); - change1(str); - System.out.println(str); + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 4, 5 }; + System.out.println(arr[0]); + change(arr); + System.out.println(arr[0]); } - public static void change1(StringBuffer str) { - str = new StringBuffer("abc");//输出:“公众号:Java面试通关手册” - //str.append("欢迎大家关注");//输出:公众号:Java面试通关手册欢迎大家关注 - //str.insert(3, "(编程)");//输出:公众号(编程):Java面试通关手册 - + public static void change(int[] array) { + // 将数组的第一个元素变为0 + array[0] = 0; } ``` **结果:** ``` -公众号:Java面试通关手册 -公众号:Java面试通关手册 +1 +0 ``` **解析:** +![example 2](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/3825204.jpg) -很多要这个时候要问了:StringBuffer创建的明明也是对象,那为什么输出结果依然是原来的值呢? - -因为在`change1`方法内部我们是新建了一个StringBuffer对象,所以`str`指向了另外一个地址,相应的操作也同样是指向另外的地址的。 +array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的时同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。 -那么,如果将`change1`方法改成如下图所示,想必大家应该知道输出什么了,如果你还不知道,那可能就是我讲的有问题了,我反思(开个玩笑,上面程序中已经给出答案): -``` - public static void change1(StringBuffer str) { +**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。** - str.append("欢迎大家关注"); - str.insert(3, "(编程)"); - - } -``` +**很多程序设计语言(特别是,C++和Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为Java程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。** -#### 2. String类型传递 +### example 3 ```java - // 测试引用传递:Sring - @org.junit.Test - public void method2() { - String str = new String("公众号:Java面试通关手册"); - System.out.println(str); - change2(str); - System.out.println(str); - } +public class Test { - public static void change2(String str) { - // str="abc"; //输出:公众号:Java面试通关手册 - str = new String("abc"); //输出:公众号:Java面试通关手册 + public static void main(String[] args) { + // TODO Auto-generated method stub + Student s1 = new Student("小张"); + Student s2 = new Student("小李"); + Test.swap(s1, s2); + System.out.println("s1:" + s1.getName()); + System.out.println("s2:" + s2.getName()); } + public static void swap(Student x, Student y) { + Student temp = x; + x = y; + y = temp; + System.out.println("x:" + x.getName()); + System.out.println("y:" + y.getName()); + } +} ``` **结果:** ``` -公众号:Java面试通关手册 -公众号:Java面试通关手册 +x:小李 +y:小张 +s1:小张 +s2:小李 ``` -可以看到不论是执行`str="abc;"`还是`str = new String("abc");`str的输出的值都不变。 -按照我们上面讲“StringBuffer类型传递”的时候说的,`str="abc;"`应该会让str的输出的值都不变。为什么呢?因为String在创建之后是不可变的。 +**解析:** -#### 3. 一道类似的题目 +交换之前: -下面的程序输出是什么? -```java -public class Demo { - public static void main(String[] args) { - Person p = new Person("张三"); +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/88729818.jpg) - change(p); +交换之后: - System.out.println(p.name); - } +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-27/34384414.jpg) - public static void change(Person p) { - Person person = new Person("李四"); - p = person; - } -} -class Person { - String name; +通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap方法的参数x和y被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝** - public Person(String name) { - this.name = name; - } -} -``` -很明显仍然会输出`张三`。因为`change`方法中重新创建了一个`Person`对象。 +### 总结 -那么,如果把` change`方法改为下图所示,输出结果又是什么呢? +Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按 +值传递的。 -```java - public static void change(Person p) { - p.name="李四"; - } -``` -答案我就不说了,我觉得大家如果认真看完上面的内容之后应该很很清楚了。 +下面再总结一下Java中方法参数的使用情况: + +- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》 +- 一个方法可以改变一个对象参数的状态。 +- 一个方法不能让对象参数引用一个新的对象。 + + +### 参考: + +《Java核心技术卷Ⅰ》基础知识第十版第四章4.5小节 ## 二 ==与equals(重要) **==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) **equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: + - 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。 - 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 @@ -226,6 +181,7 @@ public class test1 { ``` **说明:** + - String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 - 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。 @@ -236,7 +192,27 @@ public class test1 { 面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” ### hashCode()介绍 -hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。 +hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。 + +```java + /** + * Returns a hash code value for the object. This method is + * supported for the benefit of hash tables such as those provided by + * {@link java.util.HashMap}. + *

+ * As much as is reasonably practical, the hashCode method defined by + * class {@code Object} does return distinct integers for distinct + * objects. (This is typically implemented by converting the internal + * address of the object into an integer, but this implementation + * technique is not required by the + * Java™ programming language.) + * + * @return a hash code value for this object. + * @see java.lang.Object#equals(java.lang.Object) + * @see java.lang.System#identityHashCode + */ + public native int hashCode(); +``` 散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" "b/EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" similarity index 83% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" rename to "EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" index 2f6712bd320..426498cb2d9 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\344\272\214\345\221\250(2018-8-13).md" +++ "b/EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\344\272\214\345\221\250(2018-8-13).md" @@ -5,23 +5,38 @@ **可变性**   -String类中使用字符数组:`private final char value[]`保存字符串,所以String对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。 -   + +简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 + +StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 + +AbstractStringBuilder.java + +```java +abstract class AbstractStringBuilder implements Appendable, CharSequence { + char[] value; + int count; + AbstractStringBuilder() { + } + AbstractStringBuilder(int capacity) { + value = new char[capacity]; + } +``` + **线程安全性** -String中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。 +String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。    **性能** -每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 +每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 **对于三者使用的总结:** - -- 如果要操作少量的数据用 = String -- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder -- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer +1. 操作少量的数据 = String +2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder +3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer #### String为什么是不可变的吗? 简单来说就是String类利用了final修饰的char类型数组存储字符,源码如下图所以: @@ -158,6 +173,8 @@ Java语言通过字节码的方式,在一定程度上解决了传统解释型 4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 5. 接口不能用new实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 +注意:Java8 后接口可以有默认实现( default )。 + ### 成员变量与局部变量的区别有那些? 1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被public,private,static等修饰符所修饰,而局部变量不能被访问控制修饰符及static所修饰;但是,成员变量和局部变量都能被final所修饰; diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" "b/EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" similarity index 97% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" rename to "EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" index c9a6ebf38d5..3cb02d73d5b 100644 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\234\200\346\234\200\346\234\200\345\270\270\350\247\201\347\232\204Java\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223/\347\254\254\345\233\233\345\221\250(2018-8-30).md" +++ "b/EssentialContentForInterview/MostCommonJavaInterviewQuestions/\347\254\254\345\233\233\345\221\250(2018-8-30).md" @@ -20,7 +20,7 @@ 2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。 3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。 4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: - - **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。 + - **(一). 等待阻塞**:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waiting queue)中。 - **(二). 同步阻塞**:运行(running)的线程在获取对象的同步锁时,若该同步 锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 - **(三). 其他阻塞**: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。 @@ -82,10 +82,10 @@ public class Run { ![结果](https://user-gold-cdn.xitu.io/2018/3/20/16243e80f22a2d54?w=161&h=54&f=jpeg&s=7380) 从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。 -###②实现Runnable接口 +### ②实现Runnable接口 推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口。 -。 +可以通过调用 Thead 类的 `setDaemon(true)` 方法设置当前的线程为守护线程。 注意事项: @@ -178,7 +178,7 @@ Thread类中包含的成员变量代表了线程的某些优先级。如**Thread - 两者最主要的区别在于:**sleep方法没有释放锁,而wait方法释放了锁** 。 - 两者都可以暂停线程的执行。 - Wait通常被用于线程间交互/通信,sleep通常被用于暂停执行。 -- wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者nofifyAl()方法。sleep()方法执行完成后,线程会自动苏醒。 +- wait()方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒。 ## 9 为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? diff --git a/EssentialContentForInterview/PreparingForInterview/JavaInterviewLibrary.md b/EssentialContentForInterview/PreparingForInterview/JavaInterviewLibrary.md new file mode 100644 index 00000000000..4901f88932d --- /dev/null +++ b/EssentialContentForInterview/PreparingForInterview/JavaInterviewLibrary.md @@ -0,0 +1,78 @@ +最近浏览 Github ,收藏了一些还算不错的 Java面试/学习相关的仓库,分享给大家,希望对你有帮助。我暂且按照目前的 Star 数量来排序。 + +本文由 SnailClimb 整理,如需转载请联系作者。 + +### 1. interviews + +- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md) +- star: 31k +- 介绍: 软件工程技术面试个人指南。 +- 概览: + + ![interviews](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/47663247.jpg) + +### 2. JCSprout + +- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout) +- star: 17.7k +- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。 +- 概览: + + ![ JCSprout](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/85903384.jpg) + +### 3. JavaGuide + +- Github地址: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) +- star: 17.4k +- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 +- 概览: + + ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) + +### 4. technology-talk + +- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk) +- star: 4.2k +- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。 + +### 5. fullstack-tutorial + +- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial) +- star: 2.8k +- 介绍: Full Stack Developer Tutorial,后台技术栈/全栈开发/架构师之路,秋招/春招/校招/面试。 from zero to hero。 +- 概览: + + ![fullstack-tutorial](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/67104534.jpg) + +### 6. java-bible + +- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible) +- star: 1.9k +- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。 +- 概览: + + ![ java-bible](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/90223588.jpg) + +### 7. EasyJob + +- Github地址:[https://github.com/it-interview/EasyJob](https://github.com/it-interview/EasyJob) +- star: 1.9k +- 介绍: 互联网求职面试题、知识点和面经整理。 + +### 8. advanced-java + +- Github地址:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) +- star: 1k +- 介绍: 互联网 Java 工程师进阶知识完全扫盲 + +### 9. 3y + +- Github地址:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y) +- star: 0.4 k +- 介绍: Java 知识整合。 + +除了这九个仓库,再推荐几个不错的学习方向的仓库给大家。 + +1. Star 数高达 4w+的 CS 笔记-CS-Notes:[https://github.com/CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes) +2. 后端(尤其是Java)程序员的 Linux 学习仓库-Linux-Tutorial:[https://github.com/judasn/Linux-Tutorial](https://github.com/judasn/Linux-Tutorial)( Star:4.6k) +3. 两个算法相关的仓库,刷 Leetcode 的小伙伴必备:①awesome-java-leetcode:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode);②LintCode:[https://github.com/awangdev/LintCode](https://github.com/awangdev/LintCode) diff --git a/EssentialContentForInterview/PreparingForInterview/JavaProgrammerNeedKnow.md b/EssentialContentForInterview/PreparingForInterview/JavaProgrammerNeedKnow.md new file mode 100644 index 00000000000..ef111f4cf15 --- /dev/null +++ b/EssentialContentForInterview/PreparingForInterview/JavaProgrammerNeedKnow.md @@ -0,0 +1,81 @@ +  身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学那些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指名一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。 + +### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗? + +  我自己也是非985非211学校的,结合自己的经历以及一些朋友的经历,我觉得让我回答这个问题再好不过。 + +  首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。 + +  企业HR肯定是更喜欢高学历的人,毕竟985,211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢? +   +  双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。** + + +### Question2:非计算机专业的学生能学好Java后台吗?我能进大厂吗? + +  当然可以!现在非科班的程序员很多,很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班,我觉得他们很多人学的都还不错。另外,我的一个朋友本科是机械专业,大一开始自学安卓,技术贼溜,在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答,即使你是非科班程序员,如果你想进入大厂的话,你也可以通过自己的其他优势来弥补。 + +  我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。 + +  建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据机构与算法!一定学好数据机构与算法!一定学好数据机构与算法!”,重要的东西说3遍。 + + + +### Question3: 我没有实习经历的话找工作是不是特别艰难? + +  没有实习经历没关系,只要你有拿得出手的项目或者大赛经历的话,你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历,单纯就是凭借自己的项目经验撑起了整个面试。 + +  如果你既没有实习经历,又没有拿得出手的项目或者大赛经历的话,我觉得在简历关,除非你有其他特别的亮点,不然,你应该就会被刷。 + +### Question4: 我该如何准备面试呢?面试的注意事项有哪些呢? + +下面是我总结的一些准备面试的Tips以及面试必备的注意事项: + +1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账); +2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。) +3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。 +4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) +5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 +6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图;②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 +7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。 + + +**一些还算不错的 Java面试/学习相关的仓库,相信对大家准备面试一定有帮助:**[盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd) + +### Question5: 我该自学还是报培训班呢? + +  我本人更加赞同自学(你要知道去了公司可没人手把手教你了,而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见,你学个东西还要去培训班,说明什么,同等水平下,你的自学能力以及自律能力一定是比不上自学的人的)。但是如果,你连每天在寝室坚持学上8个小时以上都坚持不了,或者总是容易半途而废的话,我还是推荐你去培训班。观望身边同学去培训班的,大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。 + +  另外,如果自律能力不行,你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。 + +  总结:去不去培训班主要还是看自己,如果自己能坚持自学就自学,坚持不下来就去培训班。 + +### Question6: 没有项目经历/博客/Github开源项目怎么办? + +  从现在开始做! + +  网上有很多非常不错的项目视频,你就跟着一步一步做,不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。 + +  如果有自己的博客,也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客,当然,你也可以自己搭建一个博客(采用 Hexo+Githu Pages 搭建非常简单)。写一些什么?学习笔记、实战内容、读书笔记等等都可以。 + +  多用 Github,用好 Github,上传自己不错的项目,写好 readme 文档,在其他技术社区做好宣传。相信你也会收获一个不错的开源项目! + + +### Question7: 大厂到底青睐什么样的应届生? + +  从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求,我们大概可以总结归纳出下面这 4 点能给简历增加很多分数: + +- 参加过竞赛(含金量超高的是ACM); +- 对数据结构与算法非常熟练; +- 参与过实际项目(比如学校网站); +- 参与过某个知名的开源项目或者自己的某个开源项目很不错; + +  除了我上面说的这三点,在面试Java工程师的时候,下面几点也提升你的个人竞争力: + +- 熟悉Python、Shell、Perl等脚本语言; +- 熟悉 Java 优化,JVM调优; +- 熟悉 SOA 模式; +- 熟悉自己所用框架的底层知识比如Spring; +- 了解分布式一些常见的理论; +- 具备高并发开发经验;大数据开发经验等等。 + diff --git a/EssentialContentForInterview/PreparingForInterview/books.md b/EssentialContentForInterview/PreparingForInterview/books.md new file mode 100644 index 00000000000..34f495fd798 --- /dev/null +++ b/EssentialContentForInterview/PreparingForInterview/books.md @@ -0,0 +1,66 @@ + +### 核心基础知识 + +- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 +- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 +- [《数据结构与算法分析:C语言描述》](https://book.douban.com/subject/1139426/)(推荐,豆瓣评分 8.9,1.6K+人评价):本书是《Data Structures and Algorithm Analysis in C》一书第2版的简体中译本。原书曾被评为20世纪顶尖的30部计算机著作之一,作者Mark Allen Weiss在数据结构和算法分析方面卓有建树,他的数据结构和算法分析的著作尤其畅销,并受到广泛好评.已被世界500余所大学用作教材。 +- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! +- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 + + + + +### Java相关 + +- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 +- [《Head First Java.第二版》](https://book.douban.com/subject/2000732/)(推荐,豆瓣评分 8.7,1.0K+人评价): 可以说是我的Java启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 +- [《Java多线程编程核心技术》](https://book.douban.com/subject/26555197/): Java多线程入门级书籍还不错,但是说实话,质量不是很高,很快就可以阅读完。 +- [《JAVA网络编程 第4版》](https://book.douban.com/subject/26259017/): 可以系统的学习一下网络的一些概念以及网络编程在Java中的使用。 +- [《Java核心技术卷1+卷2》](https://book.douban.com/subject/25762168/)(推荐): 很棒的两本书,建议有点Java基础之后再读,介绍的还是比较深入的,非常推荐。这两本书我一般也会用来巩固知识点,是两本适合放在自己身边的好书。 +- [《Java编程思想(第4版)》](https://book.douban.com/subject/2130190/)(推荐,豆瓣评分 9.1,3.2K+人评价):这本书要常读,初学者可以快速概览,中等程序员可以深入看看java,老鸟还可以用之回顾java的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 +- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 +- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 +- [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/): 很杂,我只看了前面几章,不太推荐阅读。 +- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 +- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! +- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 + +### JavaWeb相关 + +- [《深入分析Java Web技术内幕》](https://book.douban.com/subject/25953851/): 感觉还行,涉及的东西也蛮多。 +- [《Spring实战(第4版)》](https://book.douban.com/subject/26767354/)(推荐,豆瓣评分 8.3 +,0.3K+人评价):不建议当做入门书籍读,入门的话可以找点国人的书或者视频看。这本定位就相当于是关于Spring的新华字典,只有一些基本概念的介绍和示例,涵盖了Spring的各个方面,但都不够深入。就像作者在最后一页写的那样:“学习Spring,这才刚刚开始”。 +- [《Java Web整合开发王者归来》](https://book.douban.com/subject/4189495/)(已过时):当时刚开始学的时候就是开的这本书,基本上是完完整整的看完了。不过,我不是很推荐大家看。这本书比较老了,里面很多东西都已经算是过时了。不过,这本书的一个很大优点是:基础知识点概括全面。 +- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 +- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) +- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 +- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 +- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 +- [《RabbitMQ实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一 +- [《Spring Cloud微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 +- [《第一本Docker书》](https://book.douban.com/subject/26780404/):Docker入门书籍! + +### 操作系统 + +- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 + +### 架构相关 + +- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 +- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 +- [《架构解密从分布式到微服务(Leaderus著)》](https://book.douban.com/subject/27081188/):很一般的书籍,我就是当做课后图书来阅读的。 + +### 代码优化 + +- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 + +### 课外书籍 + +- 《追风筝的人》(推荐) +- 《穆斯林的葬礼》 (推荐) +- 《三体》 (推荐) +- 《活着——余华》 (推荐) + + + + diff --git a/EssentialContentForInterview/PreparingForInterview/interviewPrepare.md b/EssentialContentForInterview/PreparingForInterview/interviewPrepare.md new file mode 100644 index 00000000000..c99ca1c156e --- /dev/null +++ b/EssentialContentForInterview/PreparingForInterview/interviewPrepare.md @@ -0,0 +1,74 @@ +这是【备战春招/秋招系列】的第二篇文章,主要是简单地介绍如何去准备面试。 + +不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。 + +### 1 如何获取大厂面试机会? + +**在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。** + +1. **招聘人数** :秋招多于春招 ; +2. **招聘时间** : 秋招一般7月左右开始,大概一直持续到10月底。但是大厂(如BAT)都会早开始早结束,所以一定要把握好时间。春招最佳时间为3月,次佳时间为4月,进入5月基本就不会再有春招了(金三银四)。 +3. **应聘难度** :秋招略大于春招; +4. **招聘公司:** 秋招数量多,而春招数量较少,一般为秋招的补充。 + +**综上,一般来说,秋招的含金量明显是高于春招的。** + +**下面我就说一下我自己知道的一些方法,不过应该也涵盖了大部分获取面试机会的方法。** + +1. **关注大厂官网,随时投递简历(走流程的网申);** +2. **线下参加宣讲会,直接投递简历;** +3. **找到师兄师姐/认识的人,帮忙内推(能够让你避开网申简历筛选,笔试筛选,还是挺不错的,不过也还是需要你的简历够棒);** +4. **博客发文被看中/Github优秀开源项目作者,大厂内部人员邀请你面试;** +5. **求职类网站投递简历(不是太推荐,适合海投);** + + +除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。 + +### 2 面试前的准备 + +### 2.1 准备自己的自我介绍 + +从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。 + +我这里简单分享一下我自己的自我介绍的一个简单的模板吧: + +> 面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。 + +### 2.2 关于着装 + +穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 + +### 2.3 随身带上自己的成绩单和简历 + +有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。 + +### 2.4 如果需要笔试就提前刷一些笔试题 + +平时空闲时间多的可以刷一下笔试题目(牛客网上有很多)。但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。 + +### 2.5 花时间一些逻辑题 + +面试中发现有些公司都有逻辑题测试环节,并且都把逻辑笔试成绩作为很重要的一个参考。 + +### 2.6 准备好自己的项目介绍 + +如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑: + +1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图) +2. 在这个项目中你负责了什么、做了什么、担任了什么角色 +3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 +4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 + +### 2.7 提前准备技术面试 + +搞清楚自己面试中可能涉及哪些知识点、那些知识点是重点。面试中哪些问题会被经常问到、自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) + +### 2.7 面试之前做好定向复习 + +所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。 + +举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 + +# 3 面试之后复盘 + +如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! \ No newline at end of file diff --git "a/EssentialContentForInterview/PreparingForInterview/\345\246\202\346\236\234\351\235\242\350\257\225\345\256\230\351\227\256\344\275\240\342\200\234\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\351\227\256\346\210\221\345\220\227\357\274\237\342\200\235\346\227\266\357\274\214\344\275\240\350\257\245\345\246\202\344\275\225\345\233\236\347\255\224.md" "b/EssentialContentForInterview/PreparingForInterview/\345\246\202\346\236\234\351\235\242\350\257\225\345\256\230\351\227\256\344\275\240\342\200\234\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\351\227\256\346\210\221\345\220\227\357\274\237\342\200\235\346\227\266\357\274\214\344\275\240\350\257\245\345\246\202\344\275\225\345\233\236\347\255\224.md" new file mode 100644 index 00000000000..d4d6b64b0a5 --- /dev/null +++ "b/EssentialContentForInterview/PreparingForInterview/\345\246\202\346\236\234\351\235\242\350\257\225\345\256\230\351\227\256\344\275\240\342\200\234\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\351\227\256\346\210\221\345\220\227\357\274\237\342\200\235\346\227\266\357\274\214\344\275\240\350\257\245\345\246\202\344\275\225\345\233\236\347\255\224.md" @@ -0,0 +1,64 @@ +我还记得当时我去参加面试的时候,几乎每一场面试,特别是HR面和高管面的时候,面试官总是会在结尾问我:“问了你这么多问题了,你有什么问题问我吗?”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗?不问的话面试官会不会对我影响不好?问什么问题?问这个问题会不会让面试官对我的影响不好啊? + +![无奈](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/无奈.jpg) + +### 这个问题对最终面试结果的影响到底大不大? + +就技术面试而言,回答这个问题的时候,只要你不是触碰到你所面试的公司的雷区,那么我觉得这对你能不能拿到最终offer来说影响确实是不大的。我说这些并不代表你就可以直接对面试官说:“我没问题了。”,笔主当时面试的时候确实也说过挺多次“没问题要问了。”,最终也没有导致笔主被pass掉(可能是前面表现比较好,哈哈,自恋一下)。我现在回想起来,觉得自己当时做法其实挺不对的。面试本身就是一个双向选择的过程,你对这个问题的回答也会侧面反映出你对这次面试的上心程度,你的问题是否有价值,也影响了你最终的选择与公司是否选择你。 + +面试官在技术面试中主要考察的还是你这样个人到底有没有胜任这个工作的能力以及你是否适合公司未来的发展需要,很多公司还需要你认同它的文化,我觉得你只要不是太笨,应该不会栽在这里。除非你和另外一个人在能力上相同,但是只能在你们两个人中选一个,那么这个问题才对你能不能拿到offer至关重要。有准备总比没准备好,给面试官留一个好的影响总归是没错的。 + +但是,就非技术面试来说,我觉得好好回答这个问题对你最终的结果还是比较重要的。 + +总的来说不管是技术面试还是非技术面试,如果你想赢得公司的青睐和尊重,我觉得我们都应该重视这个问题。 + +### 真诚一点,不要问太 Low 的问题 + +回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题,也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子,特别是那种特别有经验的面试官,你是真心诚意的问问题,还是从别处照搬问题来讨好面试官,人家可能一听就听出来了。总的来说,还是要真诚。除此之外,不要问太Low的问题,会显得你整个人格局比较小或者说你根本没有准备(侧面反映你对这家公司不伤心,既然你不上心,为什么要要你呢)。举例几个比较 Low 的问题,大家看看自己有没有问过其中的问题: + +- 贵公司的主要业务是什么?(面试之前自己不知道提前网上查一下吗?) +- 贵公司的男女比例如何?(考虑脱单?记住你是来工作的!) +- 贵公司一年搞几次外出旅游?(你是来工作的,这些娱乐活动先别放在心上!) +- ...... + +### 有哪些有价值的问题值得问? + +针对这个问题。笔主专门找了几个专门做HR工作的小哥哥小姐姐们询问并且查阅了挺多前辈们的回答,然后结合自己的实际经历,我概括了下面几个比较适合问的问题。 + +#### 面对HR或者其他Level比较低的面试官时 + +1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答,不会让面试官陷入无话可说的尴尬境地。另外,从面试官的回答中你可以加深对这个公司的了解,让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外,这样的问题在某种程度上还可以拉进你与面试官的距离。) +2. **能不能问一下,你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗?** (类似第一个问题,都是问面试官个人对于公司的看法,) +3. **我觉得我这次表现的不是太好,你有什么建议或者评价给我吗?**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说,这样可以显的你比较谦虚好学上进。) +4. **接下来我会有一段空档期,有什么值得注意或者建议学习的吗?** (体现出你对工作比较上心,自助学习意识比较强。) +5. **这个岗位为什么还在招人?** (岗位真实性和价值咨询) +6. **大概什么时候能给我回复呢?** (终面的时候,如果面试官没有说的话,可以问一下) +7. ...... + + + +#### 面对部门领导 + +1. **部门的主要人员分配以及对应的主要工作能简单介绍一下吗?** +2. **未来如果我要加入这个团队,你对我的期望是什么?** (部门领导一般情况下是你的直属上级了,你以后和他打交道的机会应该是最多的。你问这个问题,会让他感觉你是一个对他的部门比较上心,比较有团体意识,并且愿意倾听的候选人。) +3. **公司对新入职的员工的培养机制是什么样的呢?** (正规的公司一般都有培养机制,提前问一下是对你自己的负责也会显的你比较上心) +4. **以您来看,这个岗位未来在公司内部的发展如何?** (在我看来,问这个问题也是对你自己的负责吧,谁不想发展前景更好的岗位呢?) +5. **团队现在面临的最大挑战是什么?** (这样的问题不会暴露你对公司的不了解,并且也能让你对未来工作的挑战或困难有一个提前的预期。) + + + +#### 面对Level比较高的(比如总裁,老板) + +1. **贵公司的发展目标和方向是什么?** (看下公司的发展是否满足自己的期望) +2. **与同行业的竞争者相比,贵公司的核心竞争优势在什么地方?** (充分了解自己的优势和劣势) +3. **公司现在面临的最大挑战是什么?** + +### 来个补充,顺便送个祝福给大家 + +薪酬待遇和相关福利问题一般在终面的时候(最好不要在前面几面的时候就问到这个问题),面试官会提出来或者在面试完之后以邮件的形式告知你。一般来说,如果面试官很愿意为你回答问题,对你的问题也比较上心的话,那他肯定是觉得你就是他们要招的人。 + +大家在面试的时候,可以根据自己对于公司或者岗位的了解程度,对上面提到的问题进行适当修饰或者修改。上面提到的一些问题只是给没有经验的朋友一个参考,如果你还有其他比较好的问题的话,那当然也更好啦! + +金三银四。过了二月就到了面试高峰期或者说是黄金期。几份惊喜几份愁,愿各位能始终不忘初心!每个人都有每个人的难处。引用一句《阿甘正传》里面的台词:“生活就像一盒巧克力,你永远不知道下一块是什么味道“。 + +![加油!彩虹就要来了](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/生活就像一盒巧克力你永远不知道下一块是什么味道.JPEG) \ No newline at end of file diff --git "a/EssentialContentForInterview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" "b/EssentialContentForInterview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" new file mode 100644 index 00000000000..d07fa52a7e7 --- /dev/null +++ "b/EssentialContentForInterview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" @@ -0,0 +1,106 @@ +# 程序员的简历就该这样写 + +### 1 前言 +一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。 + +### 2 为什么说简历很重要? + +#### 2.1 先从面试前来说 + +假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。 + +假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。 + +另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。 + +所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。 + +#### 2.2 再从面试中来说 + +我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。 + +所以,首先,你要明确的一点是:**你不会的东西就不要写在简历上**。另外,**你要考虑你该如何才能让你的亮点在简历中凸显出来**,比如:你在某某项目做了什么事情解决了什么问题(只要有项目就一定有要解决的问题)、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。 + +面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。 + +### 3 下面这几点你必须知道 + +1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。 +2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作** +3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;** +4. **将自己的项目经历完美的展示出来非常重要。** + +### 4 必须了解的两大法则 + + +**①STAR法则(Situation Task Action Result):** + +- **Situation:** 事情是在什么情况下发生; +- **Task::** 你是如何明确你的任务的; +- **Action:** 针对这样的情况分析,你采用了什么行动方式; +- **Result:** 结果怎样,在这样的情况下你学习到了什么。 + +简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。 + +下面这段内容摘自百度百科,我觉得写的非常不错: + +> STAR法则,500强面试题回答时的技巧法则,备受面试者成功者和500强HR的推崇。 +由于这个法则被广泛应用于面试问题的回答,尽管我们还在写简历阶段,但是,写简历时能把面试的问题就想好,会使自己更加主动和自信,做到简历,面试关联性,逻辑性强,不至于在一个月后去面试,却把简历里的东西都忘掉了(更何况有些朋友会稍微夸大简历内容) +在我们写简历时,每个人都要写上自己的工作经历,活动经历,想必每一个同学,都会起码花上半天甚至更长的时间去搜寻脑海里所有有关的经历,争取找出最好的东西写在简历上。 +但是此时,我们要注意了,简历上的任何一个信息点都有可能成为日后面试时的重点提问对象,所以说,不能只管写上让自己感觉最牛的经历就完事了,要想到今后,在面试中,你所写的经历万一被面试官问到,你真的能回答得流利,顺畅,且能通过这段经历,证明自己正是适合这个职位的人吗? + +**②FAB 法则(Feature Advantage Benefit):** + +- **Feature:** 是什么; +- **Advantage:** 比别人好在哪些地方; +- **Benefit:** 如果雇佣你,招聘方会得到什么好处。 + +简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。 + +### 5 项目经历怎么写? + +简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: + +1. 对项目整体设计的一个感受 +2. 在这个项目中你负责了什么、做了什么、担任了什么角色 +3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 +4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。 + +### 6 专业技能该怎么写? +先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善): + +- 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握 +- Java 基础知识:掌握 +- JVM 虚拟机(Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理):掌握 +- 高并发、高可用、高性能系统开发:掌握 +- Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握 +- SSH 整合、SSM 整合、 SOA 架构:掌握 +- Dubbo: 掌握 +- Zookeeper: 掌握 +- 常见消息队列: 掌握 +- Linux:掌握 +- MySQL常见优化手段:掌握 +- Spring Boot +Spring Cloud +Docker:了解 +- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解 +- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉 + +### 7 开源程序员Markdown格式简历模板分享 + +分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 +Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) + + +我的下面这篇文章讲了如何写一份Markdown格式的简历,另外,文中还提到了一种实现 Markdown 格式到PDF、HTML、JPEG这几种格式的转换方法。 + +[手把手教你用Markdown写一份高质量的简历](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484347&idx=1&sn=a986ea7e199871999a5257bd3ed78be1&chksm=fd9855dacaefdccc2c5d5f8f79c4aa1b608ad5b42936bccaefb99a850a2e6e8e2e910e1b3153&token=719595858&lang=zh_CN#rd) + +### 8 其他的一些小tips + +1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 +2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。 +3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 +4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。 +5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 +6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 +7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 +8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 diff --git "a/EssentialContentForInterview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/EssentialContentForInterview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..0efdd618688 --- /dev/null +++ "b/EssentialContentForInterview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" @@ -0,0 +1,950 @@ + + +- [一 基础篇](#一-基础篇) + - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么) + - [2. 说一下转发\(Forward\)和重定向\(Redirect\)的区别](#2-说一下转发forward和重定向redirect的区别) + - [3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入url地址到显示主页的过程整个过程会使用哪些协议) + - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手) + - [为什么要三次握手](#为什么要三次握手) + - [为什么要传回 SYN](#为什么要传回-syn) + - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) + - [为什么要四次挥手](#为什么要四次挥手) + - [5. IP地址与MAC地址的区别](#5-ip地址与mac地址的区别) + - [6. HTTP请求,响应报文格式](#6-http请求响应报文格式) + - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql索引主要使用的两种数据结构什么是覆盖索引) + - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不) + - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式) + - [10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗?](#10-简单介绍一下bean知道spring的bean的作用域与生命周期吗) + - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量) + - [事务传播行为](#事务传播行为) + - [隔离级别](#隔离级别) + - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗) + - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理) +- [二 进阶篇](#二-进阶篇) + - [1 消息队列MQ的套路](#1-消息队列mq的套路) + - [1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处](#11-介绍一下消息队列mq的应用场景使用消息队列的好处) + - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能) + - [2)降低系统耦合性](#2降低系统耦合性) + - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗) + - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢) + - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望) + - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别) + - [2.1 两者的对比](#21-两者的对比) + - [2.2 关于两者的总结](#22-关于两者的总结) + - [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧) + - [3.1 Arraylist 与 LinkedList 有什么不同?\(注意加上从数据结构分析的内容\)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容) + - [3.2 HashMap的底层实现](#32-hashmap的底层实现) + - [1)JDK1.8之前](#1jdk18之前) + - [2)JDK1.8之后](#2jdk18之后) + - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解) + - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了) + - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别) +- [三 终结篇](#三-终结篇) + - [1. Object类有哪些方法?](#1-object类有哪些方法) + - [1.1 Object类的常见方法总结](#11-object类的常见方法总结) + - [1.2 hashCode与equals](#12-hashcode与equals) + - [1.2.1 hashCode\(\)介绍](#121-hashcode介绍) + - [1.2.2 为什么要有hashCode](#122-为什么要有hashcode) + - [1.2.3 hashCode\(\)与equals\(\)的相关规定](#123-hashcode与equals的相关规定) + - [1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的?](#124-为什么两个对象有相同的hashcode值它们也不一定是相等的) + - [1.3 ==与equals](#13-与equals) + - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题) + - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别) + - [2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap线程安全的具体实现方式底层具体实现) + - [JDK1.7\(上面有示意图\)](#jdk17上面有示意图) + - [JDK1.8\(上面有示意图\)](#jdk18上面有示意图) + - [3 谈谈 synchronized 和 ReenTrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别) + - [4 线程池了解吗?](#4-线程池了解吗) + - [4.1 为什么要用线程池?](#41-为什么要用线程池) + - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么) + - [Java 主要提供了下面4种线程池](#java-主要提供了下面4种线程池) + - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍) + - [4.3 创建的线程池的方式](#43-创建的线程池的方式) + - [5 Nginx](#5-nginx) + - [5.1 简单介绍一下Nginx](#51-简单介绍一下nginx) + - [反向代理](#反向代理) + - [负载均衡](#负载均衡) + - [动静分离](#动静分离) + - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx) + - [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗) + + + + +这些问题是2018年去美团面试的同学被问到的一些常见的问题,希望对你有帮助! + +# 一 基础篇 + + +## 1. `System.out.println(3|9)`输出什么? + +正确答案:11. + +**考察知识点:&和&&;|和||** + +**&和&&:** + +共同点:两者都可做逻辑运算符。它们都表示运算符的两边都是true时,结果为true; + +不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为true,是true就继续运算右边的然后判断并输出,是false就停下来直接输出不会再运行后面的东西。 + +**|和||:** + +共同点:两者都可做逻辑运算符。它们都表示运算符的两边任意一边为true,结果为true,两边都不是true,结果就为false; + +不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为true,是true就停下来直接输出不会再运行后面的东西,是false就继续运算右边的然后判断并输出。 + +**回到本题:** + +3 | 9=0011(二进制) | 1001(二进制)=1011(二进制)=11(十进制) + +## 2. 说一下转发(Forward)和重定向(Redirect)的区别 + +**转发是服务器行为,重定向是客户端行为。** + +**转发(Forword)** 通过RequestDispatcher对象的`forward(HttpServletRequest request,HttpServletResponse response)`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。 + +```java +request.getRequestDispatcher("login_success.jsp").forward(request, response); +``` + +**重定向(Redirect)** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。 + +1. **从地址栏显示来说:** forward是服务器请求资源,服务器直接访问目标地址的URL,把那个URL的响应内容读取过来,然后把这些内容再发给浏览器.浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址. redirect是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址.所以地址栏显示的是新的URL. +2. **从数据共享来说:** forward:转发页面和转发到的页面可以共享request里面的数据. redirect:不能共享数据. +3. **从运用地方来说:** forward:一般用于用户登陆的时候,根据角色转发到相应的模块. redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等 +4. **从效率来说:** forward:高. redirect:低. + + +## 3. 在浏览器中输入url地址到显示主页的过程,整个过程会使用哪些协议 + +图片来源:《图解HTTP》: + +![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) + +总体来说分为以下几个过程: + +1. DNS解析 +2. TCP连接 +3. 发送HTTP请求 +4. 服务器处理请求并返回HTTP报文 +5. 浏览器解析渲染页面 +6. 连接结束 + +具体可以参考下面这篇文章: + +- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700) + +## 4. TCP 三次握手和四次挥手 + +为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。 + +**漫画图解:** + +图片来源:《图解HTTP》 +![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e127396541f1?w=864&h=439&f=png&s=226095) + +**简单示意图:** +![TCP三次握手](https://user-gold-cdn.xitu.io/2018/5/8/1633e14233d95972?w=542&h=427&f=jpeg&s=15088) + +- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端 +- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端 +- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端 + +#### 为什么要三次握手 + +**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。** + +第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常。 + +第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常 + +第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送接收正常 + +所以三次握手就能确认双发收发功能都正常,缺一不可。 + +#### 为什么要传回 SYN +接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。 + +> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的TCP连接,数据才可以在客户机和服务器之间传递。 + + +#### 传了 SYN,为啥还要传 ACK + +双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。 + +![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406) + +断开一个 TCP 连接则需要“四次挥手”: + +- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送 +- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号 +- 服务器-关闭与客户端的连接,发送一个FIN给客户端 +- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加1 + + +#### 为什么要四次挥手 + +任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。 + +举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 + +上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891) + + + +## 5. IP地址与MAC地址的区别 + +参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597) + +IP地址是指互联网协议地址(Internet Protocol Address)IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。 + + + +MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的MAC地址。 + +## 6. HTTP请求,响应报文格式 + + + +HTTP请求报文主要由请求行、请求头部、请求正文3部分组成 + +HTTP响应报文主要由状态行、响应头部、响应正文3部分组成 + +详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273) + +## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql索引主要使用的两种数据结构?什么是覆盖索引? + +**为什么要使用索引?** + +1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 +2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。 +3. 帮助服务器避免排序和临时表 +4. 将随机IO变为顺序IO +5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 + +**索引这么多优点,为什么不对表中的每一个列创建一个索引呢?** + +1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 +2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 +3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 + +**索引是如何提高查询速度的?** + +将无序的数据变成相对有序的数据(就像查目录一样) + +**说一下使用索引的注意事项** + +1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。 +2. 在使用InnoDB时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。 +3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描 +4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用 +5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能 + +**Mysql索引主要使用的哪两种数据结构?** + +- 哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。 +- BTree索引:Mysql的BTree索引使用的是B树中的B+Tree。但对于主要的两种存储引擎(MyISAM和InnoDB)的实现方式是不同的。 + +更多关于索引的内容可以查看我的这篇文章:[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd) + +**什么是覆盖索引?** + +如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称 +之为“覆盖索引”。我们知道在InnoDB存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作! + + +## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不? + **进程与线程的区别是什么?** + +线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 + +**进程间的几种通信方式说一下?** + + +1. **管道(pipe)**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为pipe(无名管道)和fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。 +2. **信号量(semophore)**:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 +3. **消息队列(message queue)**:消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。 +4. **信号(signal)**:信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。 +5. **共享内存(shared memory)**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的IPC方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。 +6. **套接字(socket)**:socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。 + +**线程间的几种通信方式知道不?** + +1、锁机制 + +- 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。 +- 读写锁:允许多个线程同时读共享数据,而对写操作互斥。 +- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。 + +2、信号量机制:包括无名线程信号量与有名线程信号量 + +3、信号机制:类似于进程间的信号处理。 + +线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。 + +## 9. 为什么要用单例模式?手写几种线程安全的单例模式? + +**简单来说使用单例模式可以带来下面几个好处:** + +- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销; +- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。 + +**懒汉式(双重检查加锁版本)** + +```java +public class Singleton { + + //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量 + private volatile static Singleton uniqueInstance; + private Singleton() { + } + public static Singleton getInstance() { + //检查实例,如果不存在,就进入同步代码块 + if (uniqueInstance == null) { + //只有第一次才彻底执行这里的代码 + synchronized(Singleton.class) { + //进入同步代码块后,再检查一次,如果仍是null,才创建实例 + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } +} +``` + +**静态内部类方式** + +静态内部实现的单例是懒加载的且线程安全。 + +只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。 + +```java +public class Singleton { + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + private Singleton (){} + public static final Singleton getInstance() { + return SingletonHolder.INSTANCE; + } +} +``` + +## 10. 简单介绍一下bean;知道Spring的bean的作用域与生命周期吗? + +在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。 + +Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢? 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。 + +![pring的bean的作用域](https://user-gold-cdn.xitu.io/2018/11/10/166fd45773d5dd2e?w=563&h=299&f=webp&s=27930) + +Spring的bean的生命周期以及更多内容可以查看:[一文轻松搞懂Spring中bean的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd) + + +## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量? + +#### 事务传播行为 + +事务传播行为(为了解决业务层方法之间互相调用的事务问题): +当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量: + +**支持当前事务的情况:** + +- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 +- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 +- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性) + +**不支持当前事务的情况:** + +- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。 +- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。 +- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 + +**其他情况:** + +- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。 + + +#### 隔离级别 + +TransactionDefinition 接口中定义了五个表示隔离级别的常量: + +- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别. +- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 +- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 +- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 +- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 + +## 12. SpringMVC 原理了解吗? + +![SpringMVC 原理](https://user-gold-cdn.xitu.io/2018/11/10/166fd45787394192?w=1015&h=466&f=webp&s=35352) + +客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Model)->将得到视图对象返回给用户 + +关于 SpringMVC 原理更多内容可以查看我的这篇文章:[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd) + +## 13. Spring AOP IOC 实现原理 + +过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。 + +**IOC:** 控制反转也叫依赖注入。IOC利用java反射机制,AOP利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在spring配置文件中配置对应的bean以及设置相关的属性,让spring容器来生成类的实例对象以及管理对象。在spring容器启动的时候,spring会把你在配置文件中配置的bean都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些bean分配给你需要调用这些bean的类。 + +**AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP可以说是对OOP的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。 + + + +# 二 进阶篇 + +## 1 消息队列MQ的套路 + +消息队列/消息中间件应该是Java程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花2个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可! + +### 1.1 介绍一下消息队列MQ的应用场景/使用消息队列的好处 + +面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列MQ的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。 + +**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。** + +#### 1)通过异步处理提高系统性能 +![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123) +如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。** + +通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: +![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550) +因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。 + +#### 2)降低系统耦合性 +我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和2.**分布式服务**。 + +> **先来简单说一下分布式服务:** + +目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c) + +> **再来谈我们的分布式消息队列:** + +我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 + +我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: +![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946) +**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。 + +消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 + +**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。** + +**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。 + +> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题! + +### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗? + +- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了! +- **系统复杂性提高:** 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题! +- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了! + +> 了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java工程师面试突击第1季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可! + +### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢? + + +| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafaka | +| :---------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | +| 单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 | +| topic数量对吞吐量的影响 | | | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 | +| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 | +| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 | +| 时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 | +| 功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 | +| 优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用rabbitmq也比较多一些但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量。而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 | + +> 这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java工程师面试突击第1季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java面试通关手册”后台回复关键字“1”即可! + +### 1.4 关于消息队列其他一些常见的问题展望 + +1. 引入消息队列之后如何保证高可用性 +2. 如何保证消息不被重复消费呢? +3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)? +4. 我该怎么保证从消息队列里拿到的数据按顺序执行? +5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决? +6. 如果让你来开发一个消息队列中间件,你会怎么设计架构? + + + +## 2 谈谈 InnoDB 和 MyIsam 两者的区别 + +### 2.1 两者的对比 + +1. **count运算上的区别:** 因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存 +2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 +3. **是否支持外键:** MyISAM不支持,而InnoDB支持。 + + +### 2.2 关于两者的总结 + +MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。 + +一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。 + + +## 3 聊聊 Java 中的集合吧! + +### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容) + +- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; +- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:); +- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** +- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 +- **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 + +**补充内容:RandomAccess接口** + +```java +public interface RandomAccess { +} +``` + +查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 + +在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 + +```java + public static + int binarySearch(List> list, T key) { + if (list instanceof RandomAccess || list.size() Java 中的集合这类问题几乎是面试必问的,问到这类问题的时候,HashMap 又是几乎必问的问题,所以大家一定要引起重视! + +### 3.2 HashMap的底层实现 + +#### 1)JDK1.8之前 + +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** + +**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** + +**JDK 1.8 HashMap 的 hash 方法源码:** + +JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 + +```java + static final int hash(Object key) { + int h; + // key.hashCode():返回散列值也就是hashcode + // ^ :按位异或 + // >>>:无符号右移,忽略符号位,空位都以0补齐 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } +``` +对比一下 JDK1.7的 HashMap 的 hash 方法源码. + +```java +static int hash(int h) { + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); +} +``` + +相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。 + +所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 + + + +![jdk1.8之前的内部结构](https://user-gold-cdn.xitu.io/2018/3/20/16240dbcc303d872?w=348&h=427&f=png&s=10991) + + +#### 2)JDK1.8之后 + +相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。 + +![JDK1.8之后的HashMap底层数据结构](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c351da9?w=720&h=545&f=jpeg&s=23933) + +TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 + +> 问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题! + +### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解 + +![红黑树](https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c138cba?w=851&h=614&f=jpeg&s=34458) + +**红黑树特点:** + +1. 每个节点非红即黑; +2. 根节点总是黑色的; +3. 每个叶子节点都是黑色的空节点(NIL节点); +4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定); +5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) + + +**红黑树的应用:** + +TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。 + +**为什么要用红黑树** + +简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。 + + +### 3.4 红黑树这么优秀,为何不直接使用红黑树得了? + +说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。 + +### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别 + +**HashMap 和 Hashtable 的区别** + +1. **线程是否安全:** HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!); +2. **效率:** 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它; +3. **对Null key 和Null value的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。 +4. **初始容量大小和每次扩充容量大小的不同 :** ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。 +5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。 + +**HashSet 和 HashMap 区别** + +如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。) + +![HashSet 和 HashMap 区别](https://user-gold-cdn.xitu.io/2018/3/2/161e717d734f3b23?w=896&h=363&f=jpeg&s=205536) + +# 三 终结篇 + +## 1. Object类有哪些方法? + +这个问题,面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握Java这门编程语言,大家都要掌握! + +### 1.1 Object类的常见方法总结 + +Object类是一个特殊的类,是所有类的父类。它主要提供了以下11个方法: + +```java + +public final native Class getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。 + +public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。 +public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。 + +protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。 + +public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。 + +public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。 + +public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。 + +public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。 + +public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。 + +public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念 + +protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作 + +``` + +> 问完上面这个问题之后,面试官很可能紧接着就会问你“hashCode与equals”相关的问题。 + +### 1.2 hashCode与equals + +面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” + +#### 1.2.1 hashCode()介绍 + +hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。 + +```java + public native int hashCode(); +``` + +散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) + +#### 1.2.2 为什么要有hashCode + + +**我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode:** + +当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了equals的次数,相应就大大提高了执行速度。 + + +#### 1.2.3 hashCode()与equals()的相关规定 + +1. 如果两个对象相等,则hashcode一定也是相同的 +2. 两个对象相等,对两个对象分别调用equals方法都返回true +3. 两个对象有相同的hashcode值,它们也不一定是相等的 +4. **因此,equals方法被覆盖过,则hashCode方法也必须被覆盖** +5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) + +#### 1.2.4 为什么两个对象有相同的hashcode值,它们也不一定是相等的? + +在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。 + +因为hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。 + +我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。 + +> ==与equals 的对比也是比较常问的基础问题之一! + +### 1.3 ==与equals + +**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) + +**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: + +- 情况1:类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。 +- 情况2:类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。 + + +**举个例子:** + +```java +public class test1 { + public static void main(String[] args) { + String a = new String("ab"); // a 为一个引用 + String b = new String("ab"); // b为另一个引用,对象的内容一样 + String aa = "ab"; // 放在常量池中 + String bb = "ab"; // 从常量池中查找 + if (aa == bb) // true + System.out.println("aa==bb"); + if (a == b) // false,非同一对象 + System.out.println("a==b"); + if (a.equals(b)) // true + System.out.println("aEQb"); + if (42 == 42.0) { // true + System.out.println("true"); + } + } +} +``` + +**说明:** + +- String中的equals方法是被重写过的,因为object的equals方法是比较的对象的内存地址,而String的equals方法比较的是对象的值。 +- 当创建String类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个String对象。 + +> 在[【备战春招/秋招系列5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题:HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀,为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视! + +## 2 ConcurrentHashMap 相关问题 + +### 2.1 ConcurrentHashMap 和 Hashtable 的区别 + +ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 + +- **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; +- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 + +**两者的对比图:** + +图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html + +HashTable: +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/50656681.jpg) + +JDK1.7的ConcurrentHashMap: +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/33120488.jpg) +JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 +Node: 链表节点): +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-22/97739220.jpg) + +### 2.2 ConcurrentHashMap线程安全的具体实现方式/底层具体实现 + +#### JDK1.7(上面有示意图) + +首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 + +**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 + +Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 + +```java +static class Segment extends ReentrantLock implements Serializable { +} +``` + +一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。 + +#### JDK1.8(上面有示意图) + +ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。 + +synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。 + +## 3 谈谈 synchronized 和 ReenTrantLock 的区别 + +**① 两者都是可重入锁** + +两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 + +**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** + +synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 + +**③ ReenTrantLock 比 synchronized 增加了一些高级功能** + +相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** + +- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 +- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 +- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 + +如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 + +**④ 两者的性能已经相差无几** + +在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。 + + +## 4 线程池了解吗? + + +### 4.1 为什么要用线程池? + +线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 + +这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处: + +- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 +- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 +- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 + +### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么? + +#### Java 主要提供了下面4种线程池 + +- **FixedThreadPool:** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 +- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 +- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 +- **ScheduledThreadPoolExecutor:** 主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor又分为:ScheduledThreadPoolExecutor(包含多个线程)和SingleThreadScheduledExecutor (只包含一个线程)两种。 + +#### 各种线程池的适用场景介绍 + +- **FixedThreadPool:** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器; +- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。 +- **CachedThreadPool:** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器; +- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景, +- **SingleThreadScheduledExecutor:** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。 + +### 4.3 创建的线程池的方式 + +**(1) 使用 Executors 创建** + +我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用Java提供好的线程池,另外在《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 + +```java +Executors 返回线程池对象的弊端如下: + +FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 +CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 + +``` +**(2) ThreadPoolExecutor的构造函数创建** + + +我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下: + +```java +private static ExecutorService executor = new ThreadPoolExecutor(13, 13, + 60L, TimeUnit.SECONDS, + new ArrayBlockingQueue(13)); +``` + +这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。 + +**(3) 使用开源类库** + +Hollis 大佬之前在他的文章中也提到了:“除了自己定义ThreadPoolExecutor外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。”他推荐使用guava提供的ThreadFactoryBuilder来创建线程池。下面是参考他的代码示例: + +```java +public class ExecutorsDemo { + + private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() + .setNameFormat("demo-pool-%d").build(); + + private static ExecutorService pool = new ThreadPoolExecutor(5, 200, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); + + public static void main(String[] args) { + + for (int i = 0; i < Integer.MAX_VALUE; i++) { + pool.execute(new SubThread()); + } + } +} +``` + +通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。 + +## 5 Nginx + +### 5.1 简单介绍一下Nginx + +Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。 + +#### 反向代理 + +谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了 + +- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的ip和端口号。正向代理比较常见的一个例子就是 VPN了。 +- **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。 + +通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/): + +![正向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/60925795.jpg) + +![反向代理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-15/62563930.jpg) + +所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。 + +#### 负载均衡 + +在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。 + +Nginx支持的weight轮询(默认)、ip_hash、fair、url_hash这四种负载均衡调度算法,感兴趣的可以自行查阅。 + +负载均衡相比于反向代理更侧重的时将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。 + +#### 动静分离 + +动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。 + +### 5.2 为什么要用 Nginx? + +> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。 + +如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。 + +Nginx 有以下5个优点: + +1. 高并发、高性能(这是其他web服务器不具有的) +2. 可扩展性好(模块化设计,第三方插件生态圈丰富) +3. 高可靠性(可以在服务器行持续不间断的运行数年) +4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx服务的情况下升级 Nginx) +5. BSD许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本) + +### 5.3 Nginx 的四个主要组成部分了解吗? + +> 这部分内容参考极客时间—[Nginx核心知识100讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE=)。 + +- Nginx 二进制可执行文件:由各模块源码编译出一个文件 +- Nginx.conf 配置文件:控制Nginx 行为 +- acess.log 访问日志: 记录每一条HTTP请求信息 +- error.log 错误日志:定位问题 diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" "b/EssentialContentForInterview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" similarity index 100% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" rename to "EssentialContentForInterview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\256\200\345\216\206\346\250\241\346\235\277.md" "b/EssentialContentForInterview/\347\256\200\345\216\206\346\250\241\346\235\277.md" similarity index 100% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\347\256\200\345\216\206\346\250\241\346\235\277.md" rename to "EssentialContentForInterview/\347\256\200\345\216\206\346\250\241\346\235\277.md" diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" "b/EssentialContentForInterview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" similarity index 100% rename from "\351\235\242\350\257\225\345\277\205\345\244\207/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" rename to "EssentialContentForInterview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" diff --git a/Java/ArrayList-Grow.md b/Java/ArrayList-Grow.md new file mode 100644 index 00000000000..6dd4cc93daf --- /dev/null +++ b/Java/ArrayList-Grow.md @@ -0,0 +1,347 @@ + +## 一 先从 ArrayList 的构造函数说起 + +**ArrayList有三种方式来初始化,构造方法源码如下:** + +```java + /** + * 默认初始容量大小 + */ + private static final int DEFAULT_CAPACITY = 10; + + + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + *默认构造函数,使用初始容量10构造一个空列表(无参数构造) + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * 带初始容量参数的构造函数。(用户自己指定容量) + */ + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) {//初始容量大于0 + //创建initialCapacity大小的数组 + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) {//初始容量等于0 + //创建空数组 + this.elementData = EMPTY_ELEMENTDATA; + } else {//初始容量小于0,抛出异常 + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + + /** + *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回 + *如果指定的集合为null,throws NullPointerException。 + */ + public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + +``` + +细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容! + +## 二 一步一步分析 ArrayList 扩容机制 + +这里以无参构造函数创建的 ArrayList 为例分析 + +### 1. 先来看 `add` 方法 + +```java + /** + * 将指定的元素追加到此列表的末尾。 + */ + public boolean add(E e) { + //添加元素之前,先调用ensureCapacityInternal方法 + ensureCapacityInternal(size + 1); // Increments modCount!! + //这里看到ArrayList添加元素的实质就相当于为数组赋值 + elementData[size++] = e; + return true; + } +``` +### 2. 再来看看 `ensureCapacityInternal()` 方法 + +可以看到 `add` 方法 首先调用了`ensureCapacityInternal(size + 1)` + +```java + //得到最小扩容量 + private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + // 获取默认的容量和传入参数的较大值 + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } +``` +**当 要 add 进第1个元素时,minCapacity为1,在Math.max()方法比较后,minCapacity 为10。** + +### 3. `ensureExplicitCapacity()` 方法 + +如果调用 `ensureCapacityInternal()` 方法就一定会进过(执行)这个方法,下面我们来研究一下这个方法的源码! + +```java + //判断是否需要扩容 + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0) + //调用grow方法进行扩容,调用此方法代表已经开始扩容了 + grow(minCapacity); + } + +``` + +我们来仔细分析一下: + +- 当我们要 add 进第1个元素到 ArrayList 时,elementData.length 为0 (因为还是一个空的 list),因为执行了 `ensureCapacityInternal()` 方法 ,所以 minCapacity 此时为10。此时,`minCapacity - elementData.length > 0 `成立,所以会进入 `grow(minCapacity)` 方法。 +- 当add第2个元素时,minCapacity 为2,此时e lementData.length(容量)在添加第一个元素后扩容成 10 了。此时,`minCapacity - elementData.length > 0 ` 不成立,所以不会进入 (执行)`grow(minCapacity)` 方法。 +- 添加第3、4···到第10个元素时,依然不会执行grow方法,数组容量都为10。 + +直到添加第11个元素,minCapacity(为11)比elementData.length(为10)要大。进入grow方法进行扩容。 + +### 4. `grow()` 方法 + +```java + /** + * 要分配的最大数组大小 + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * ArrayList扩容的核心方法。 + */ + private void grow(int minCapacity) { + // oldCapacity为旧容量,newCapacity为新容量 + int oldCapacity = elementData.length; + //将oldCapacity 右移一位,其效果相当于oldCapacity /2, + //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, + int newCapacity = oldCapacity + (oldCapacity >> 1); + //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + // 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE, + //如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + elementData = Arrays.copyOf(elementData, newCapacity); + } +``` + +**int newCapacity = oldCapacity + (oldCapacity >> 1),所以 ArrayList 每次扩容之后容量都会变为原来的 1.5 倍!(JDK1.6版本以后)** JDk1.6版本时,扩容之后容量为 1.5 倍+1!详情请参考源码 + +> ">>"(移位运算符):>>1 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源   + +**我们再来通过例子探究一下`grow()` 方法 :** + +- 当add第1个元素时,oldCapacity 为0,经比较后第一个if判断成立,newCapacity = minCapacity(为10)。但是第二个if判断不会成立,即newCapacity 不比 MAX_ARRAY_SIZE大,则不会进入 `hugeCapacity` 方法。数组容量为10,add方法中 return true,size增为1。 +- 当add第11个元素进入grow方法时,newCapacity为15,比minCapacity(为11)大,第一个if判断不成立。新容量没有大于数组最大size,不会进入hugeCapacity方法。数组容量扩为15,add方法中return true,size增为11。 +- 以此类推······ + +**这里补充一点比较重要,但是容易被忽视掉的知识点:** + +- java 中的 `length `属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性. +- java 中的 `length()` 方法是针对字符串说的,如果想看这个字符串的长度则用到 `length()` 这个方法. +- java 中的 `size()` 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! + +### 5. `hugeCapacity()` 方法。 + +从上面 `grow()` 方法源码我们知道: 如果新容量大于 MAX_ARRAY_SIZE,进入(执行) `hugeCapacity()` 方法来比较 minCapacity 和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为`Integer.MAX_VALUE`,否则,新容量大小则为 MAX_ARRAY_SIZE 即为 `Integer.MAX_VALUE - 8`。 + + +```java + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + //对minCapacity和MAX_ARRAY_SIZE进行比较 + //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小 + //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小 + //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } +``` + + + +## 三 `System.arraycopy()` 和 `Arrays.copyOf()`方法 + + +阅读源码的话,我们就会发现 ArrayList 中大量调用了这两个方法。比如:我们上面讲的扩容操作以及`add(int index, E element)`、`toArray()` 等方法中都用到了该方法! + + +### 3.1 `System.arraycopy()` 方法 + +```java + /** + * 在此列表中的指定位置插入指定的元素。 + *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; + *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 + */ + public void add(int index, E element) { + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + //arraycopy()方法实现数组自己复制自己 + //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + size++; + } +``` + +我们写一个简单的方法测试以下: + +```java +public class ArraycopyTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int[] a = new int[10]; + a[0] = 0; + a[1] = 1; + a[2] = 2; + a[3] = 3; + System.arraycopy(a, 2, a, 3, 3); + a[2]=99; + for (int i = 0; i < a.length; i++) { + System.out.println(a[i]); + } + } + +} +``` + +结果: + +``` +0 1 99 2 3 0 0 0 0 0 +``` + +### 3.2 `Arrays.copyOf()`方法 + +```java + /** + 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。 + */ + public Object[] toArray() { + //elementData:要复制的数组;size:要复制的长度 + return Arrays.copyOf(elementData, size); + } +``` + +个人觉得使用 `Arrays.copyOf()`方法主要是为了给原有数组扩容,测试代码如下: + +```java +public class ArrayscopyOfTest { + + public static void main(String[] args) { + int[] a = new int[3]; + a[0] = 0; + a[1] = 1; + a[2] = 2; + int[] b = Arrays.copyOf(a, 10); + System.out.println("b.length"+b.length); + } +} +``` + +结果: + +``` +10 +``` + + +### 3.3 两者联系和区别 + +**联系:** + +看两者源代码可以发现 copyOf() 内部实际调用了 `System.arraycopy()` 方法 + +**区别:** + +`arraycopy()` 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 `copyOf()` 是系统自动在内部新建一个数组,并返回该数组。 + + + +## 四 `ensureCapacity`方法 + +ArrayList 源码中有一个 `ensureCapacity` 方法不知道大家注意到没有,这个方法 ArrayList 内部没有被调用过,所以很显然是提供给用户调用的,那么这个方法有什么作用呢? + +```java + /** + 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。 + * + * @param minCapacity 所需的最小容量 + */ + public void ensureCapacity(int minCapacity) { + int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + // any size if not default element table + ? 0 + // larger than default for default empty table. It's already + // supposed to be at default size. + : DEFAULT_CAPACITY; + + if (minCapacity > minExpand) { + ensureExplicitCapacity(minCapacity); + } + } + +``` + +**最好在 add 大量元素之前用 `ensureCapacity` 方法,以减少增量重新分配的次数** + +我们通过下面的代码实际测试以下这个方法的效果: + +```java +public class EnsureCapacityTest { + public static void main(String[] args) { + ArrayList list = new ArrayList(); + final int N = 10000000; + long startTime = System.currentTimeMillis(); + for (int i = 0; i < N; i++) { + list.add(i); + } + long endTime = System.currentTimeMillis(); + System.out.println("使用ensureCapacity方法前:"+(endTime - startTime)); + + list = new ArrayList(); + long startTime1 = System.currentTimeMillis(); + list.ensureCapacity(N); + for (int i = 0; i < N; i++) { + list.add(i); + } + long endTime1 = System.currentTimeMillis(); + System.out.println("使用ensureCapacity方法后:"+(endTime1 - startTime1)); + } +} +``` + +运行结果: + +``` +使用ensureCapacity方法前:4637 +使用ensureCapacity方法后:241 + +``` + +通过运行结果,我们可以很明显的看出向 ArrayList 添加大量元素之前最好先使用`ensureCapacity` 方法,以减少增量重新分配的次数 diff --git "a/Java\347\233\270\345\205\263/ArrayList.md" b/Java/ArrayList.md similarity index 96% rename from "Java\347\233\270\345\205\263/ArrayList.md" rename to Java/ArrayList.md index 599545820c1..c3e8dd47896 100644 --- "a/Java\347\233\270\345\205\263/ArrayList.md" +++ b/Java/ArrayList.md @@ -1,4 +1,3 @@ - - [ArrayList简介](#arraylist简介) @@ -19,15 +18,15 @@ 它继承于 **AbstractList**,实现了 **List**, **RandomAccess**, **Cloneable**, **java.io.Serializable** 这些接口。 在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为**O(n)**,求表长以及增加元素,取第 i 元素的时间复杂度为**O(1)** - +   ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。 - -  ArrayList 实现了**RandmoAccess 接口**,即提供了随机访问功能。RandmoAccess 是 Java 中用来被 List 实现,为 List 提供**快速访问功能**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 - + +  ArrayList 实现了**RandomAccess 接口**, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 +   ArrayList 实现了**Cloneable 接口**,即覆盖了函数 clone(),**能被克隆**。 - +   ArrayList 实现**java.io.Serializable 接口**,这意味着ArrayList**支持序列化**,**能通过序列化去传输**。 - +   和 Vector 不同,**ArrayList 中的操作不是线程安全的**!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。 ### ArrayList核心源码 @@ -85,7 +84,7 @@ public class ArrayList extends AbstractList } /** - *默认构造函数,其默认初始容量为10 + *默认构造函数,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10 */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; @@ -177,7 +176,7 @@ public class ArrayList extends AbstractList newCapacity = minCapacity; //再检查新容量是否超出了ArrayList所定义的最大容量, //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, - //如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为 minCapacity。 + //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: @@ -631,7 +630,7 @@ public class ArrayList extends AbstractList newCapacity = minCapacity; //再检查新容量是否超出了ArrayList所定义的最大容量, //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, - //如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为 minCapacity。 + //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: @@ -653,15 +652,15 @@ public class ArrayList extends AbstractList 3. .java 中的**size()方法**是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看! - + #### 内部类 ```java (1)private class Itr implements Iterator (2)private class ListItr extends Itr implements ListIterator (3)private class SubList extends AbstractList implements RandomAccess (4)static final class ArrayListSpliterator implements Spliterator -``` -  ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**,**next()**,**remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**,**nextIndex()**,**previousIndex()**,**previous()**,**set(E e)**,**add(E e)**等方法,所以这也可以看出了**Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。 +``` +  ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**,**next()**,**remove()**等方法;其中的**ListItr**继承**Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**,**nextIndex()**,**previousIndex()**,**previous()**,**set(E e)**,**add(E e)**等方法,所以这也可以看出了 **Iterator和ListIterator的区别:**ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法,这些是Iterator不能实现的。 ### ArrayList经典Demo ```java diff --git a/Java/BIO,NIO,AIO summary.md b/Java/BIO,NIO,AIO summary.md new file mode 100644 index 00000000000..c5ec6dddd04 --- /dev/null +++ b/Java/BIO,NIO,AIO summary.md @@ -0,0 +1,347 @@ +熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础。 + + + +- [BIO,NIO,AIO 总结](#bionioaio-总结) + - [1. BIO \(Blocking I/O\)](#1-bio-blocking-io) + - [1.1 传统 BIO](#11-传统-bio) + - [1.2 伪异步 IO](#12-伪异步-io) + - [1.3 代码示例](#13-代码示例) + - [1.4 总结](#14-总结) + - [2. NIO \(New I/O\)](#2-nio-new-io) + - [2.1 NIO 简介](#21-nio-简介) + - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别) + - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io(非阻塞io)) + - [2)Buffer\(缓冲区\)](#2buffer缓冲区) + - [3)Channel \(通道\)](#3channel-通道) + - [4)Selectors\(选择器\)](#4selectors选择器) + - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式) + - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍) + - [2.5 代码示例](#25-代码示例) + - [3. AIO \(Asynchronous I/O\)](#3-aio-asynchronous-io) + - [参考](#参考) + + + + +# BIO,NIO,AIO 总结 + + Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。 + +在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。 + +**同步与异步** + +- **同步:** 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。 +- **异步:** 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。 + +同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。 + +**阻塞和非阻塞** + +- **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。 +- **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。 + +举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。 + + +## 1. BIO (Blocking I/O) + +同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。 + +### 1.1 传统 BIO + +BIO通信(一请求一应答)模型图如下(图源网络,原出处不明): + +![传统BIO通信模型图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2.png) + +采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。 + +如果要让 **BIO 通信模型** 能够同时处理多个客户端请求,就必须使用多线程(主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 **一请求一应答通信模型** 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 **线程池机制** 改善,线程池还可以让线程的创建和回收成本相对较低。使用`FixedThreadPool` 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节"伪异步 BIO"中会详细介绍到。 + +**我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?** + +在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。 + +### 1.2 伪异步 IO + +为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。 + +伪异步IO模型图(图源网络,原出处不明): + +![伪异步IO模型图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3.png) + +采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。 + +伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层任然是同步阻塞的BIO模型,因此无法从根本上解决问题。 + +### 1.3 代码示例 + +下面代码中演示了BIO通信(一请求一应答)模型。我们会在客户端创建多个线程依次连接服务端并向其发送"当前时间+:hello world",服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客,原地址如下: + +[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a) + +**客户端** + +```java +/** + * + * @author 闪电侠 + * @date 2018年10月14日 + * @Description:客户端 + */ +public class IOClient { + + public static void main(String[] args) { + // TODO 创建多个线程,模拟多个客户端连接服务端 + new Thread(() -> { + try { + Socket socket = new Socket("127.0.0.1", 3333); + while (true) { + try { + socket.getOutputStream().write((new Date() + ": hello world").getBytes()); + Thread.sleep(2000); + } catch (Exception e) { + } + } + } catch (IOException e) { + } + }).start(); + + } + +} + +``` + +**服务端** + +```java +/** + * @author 闪电侠 + * @date 2018年10月14日 + * @Description: 服务端 + */ +public class IOServer { + + public static void main(String[] args) throws IOException { + // TODO 服务端处理客户端连接请求 + ServerSocket serverSocket = new ServerSocket(3333); + + // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理 + new Thread(() -> { + while (true) { + try { + // 阻塞方法获取新的连接 + Socket socket = serverSocket.accept(); + + // 每一个新的连接都创建一个线程,负责读取数据 + new Thread(() -> { + try { + int len; + byte[] data = new byte[1024]; + InputStream inputStream = socket.getInputStream(); + // 按字节流方式读取数据 + while ((len = inputStream.read(data)) != -1) { + System.out.println(new String(data, 0, len)); + } + } catch (IOException e) { + } + }).start(); + + } catch (IOException e) { + } + + } + }).start(); + + } + +} +``` + +### 1.4 总结 + +在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。 + + + +## 2. NIO (New I/O) + +### 2.1 NIO 简介 + + NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。 + +NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。 + +### 2.2 NIO的特性/NIO与IO区别 + +如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。 + +#### 1)Non-blocking IO(非阻塞IO) + +**IO流是阻塞的,NIO流是不阻塞的。** + +Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 + +Java IO的各种流是阻塞的。这意味着,当一个线程调用 `read()` 或 `write()` 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了 + +#### 2)Buffer(缓冲区) + +**IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。** + +Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。 + +在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。 + +最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。 + +#### 3)Channel (通道) + +NIO 通过Channel(通道) 进行读写。 + +通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。 + +#### 4)Selectors(选择器) + +NIO有选择器,而IO没有。 + +选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。 + +![一个单线程中Slector维护3个Channel的示意图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Slector.png) + +### 2.3 NIO 读数据和写数据方式 +通常来说NIO中的所有IO都是从 Channel(通道) 开始的。 + +- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。 +- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。 + +数据读取和写入操作图示: + +![NIO读写数据的方式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/NIO读写数据的方式.png) + + +### 2.4 NIO核心组件简单介绍 + +NIO 包含下面几个核心的组件: + +- Channel(通道) +- Buffer(缓冲区) +- Selector(选择器) + +整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。 + +### 2.5 代码示例 + +代码示例出自闪电侠的博客,原地址如下: + +[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a) + +客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。 + +```java +/** + * + * @author 闪电侠 + * @date 2019年2月21日 + * @Description: NIO 改造后的服务端 + */ +public class NIOServer { + public static void main(String[] args) throws IOException { + // 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程, + // 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等 + Selector serverSelector = Selector.open(); + // 2. clientSelector负责轮询连接是否有数据可读 + Selector clientSelector = Selector.open(); + + new Thread(() -> { + try { + // 对应IO编程中服务端启动 + ServerSocketChannel listenerChannel = ServerSocketChannel.open(); + listenerChannel.socket().bind(new InetSocketAddress(3333)); + listenerChannel.configureBlocking(false); + listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT); + + while (true) { + // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms + if (serverSelector.select(1) > 0) { + Set set = serverSelector.selectedKeys(); + Iterator keyIterator = set.iterator(); + + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + + if (key.isAcceptable()) { + try { + // (1) + // 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector + SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept(); + clientChannel.configureBlocking(false); + clientChannel.register(clientSelector, SelectionKey.OP_READ); + } finally { + keyIterator.remove(); + } + } + + } + } + } + } catch (IOException ignored) { + } + }).start(); + new Thread(() -> { + try { + while (true) { + // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms + if (clientSelector.select(1) > 0) { + Set set = clientSelector.selectedKeys(); + Iterator keyIterator = set.iterator(); + + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + + if (key.isReadable()) { + try { + SocketChannel clientChannel = (SocketChannel) key.channel(); + ByteBuffer byteBuffer = ByteBuffer.allocate(1024); + // (3) 面向 Buffer + clientChannel.read(byteBuffer); + byteBuffer.flip(); + System.out.println( + Charset.defaultCharset().newDecoder().decode(byteBuffer).toString()); + } finally { + keyIterator.remove(); + key.interestOps(SelectionKey.OP_READ); + } + } + + } + } + } + } catch (IOException ignored) { + } + }).start(); + + } +} +``` + +为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题: + +- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100% +- 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug + +Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。 + +### 3. AIO (Asynchronous I/O) + +AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。 + +AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:[《漫话:如何给女朋友解释什么是Linux的五种IO模型?》](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect) ) + +查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。 + +## 参考 + +- 《Netty 权威指南》第二版 +- https://zhuanlan.zhihu.com/p/23488863 (美团技术团队) diff --git a/Java/Basis/Arrays,CollectionsCommonMethods.md b/Java/Basis/Arrays,CollectionsCommonMethods.md new file mode 100644 index 00000000000..5c7e705bac5 --- /dev/null +++ b/Java/Basis/Arrays,CollectionsCommonMethods.md @@ -0,0 +1,315 @@ + + +- [Collections 工具类和 Arrays 工具类常见方法](#collections-工具类和-arrays-工具类常见方法) + - [Collections](#collections) + - [排序操作](#排序操作) + - [查找,替换操作](#查找替换操作) + - [同步控制](#同步控制) + - [Arrays类的常见操作](#arrays类的常见操作) + - [排序 : `sort()`](#排序--sort) + - [查找 : `binarySearch()`](#查找--binarysearch) + - [比较: `equals()`](#比较-equals) + - [填充 : `fill()`](#填充--fill) + - [转列表 `asList()`](#转列表-aslist) + - [转字符串 `toString()`](#转字符串-tostring) + - [复制 `copyOf()`](#复制-copyof) + + +# Collections 工具类和 Arrays 工具类常见方法 + +## Collections + +Collections 工具类常用方法: + +1. 排序 +2. 查找,替换操作 +3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合) + +### 排序操作 + +```java +void reverse(List list)//反转 +void shuffle(List list)//随机排序 +void sort(List list)//按自然排序的升序排序 +void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑 +void swap(List list, int i , int j)//交换两个索引位置的元素 +void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。 +``` + +**示例代码:** + +```java + ArrayList arrayList = new ArrayList(); + arrayList.add(-1); + arrayList.add(3); + arrayList.add(3); + arrayList.add(-5); + arrayList.add(7); + arrayList.add(4); + arrayList.add(-9); + arrayList.add(-7); + System.out.println("原始数组:"); + System.out.println(arrayList); + // void reverse(List list):反转 + Collections.reverse(arrayList); + System.out.println("Collections.reverse(arrayList):"); + System.out.println(arrayList); + + + Collections.rotate(arrayList, 4); + System.out.println("Collections.rotate(arrayList, 4):"); + System.out.println(arrayList); + + // void sort(List list),按自然排序的升序排序 + Collections.sort(arrayList); + System.out.println("Collections.sort(arrayList):"); + System.out.println(arrayList); + + // void shuffle(List list),随机排序 + Collections.shuffle(arrayList); + System.out.println("Collections.shuffle(arrayList):"); + System.out.println(arrayList); + + // 定制排序的用法 + Collections.sort(arrayList, new Comparator() { + + @Override + public int compare(Integer o1, Integer o2) { + return o2.compareTo(o1); + } + }); + System.out.println("定制排序后:"); + System.out.println(arrayList); +``` + +### 查找,替换操作 + +```java +int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的 +int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll) +int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c) +void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。 +int frequency(Collection c, Object o)//统计元素出现次数 +int indexOfSubList(List list, List target)//统计targe在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target). +boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素 +``` + +**示例代码:** + +```java + ArrayList arrayList = new ArrayList(); + arrayList.add(-1); + arrayList.add(3); + arrayList.add(3); + arrayList.add(-5); + arrayList.add(7); + arrayList.add(4); + arrayList.add(-9); + arrayList.add(-7); + ArrayList arrayList2 = new ArrayList(); + arrayList2.add(-3); + arrayList2.add(-5); + arrayList2.add(7); + System.out.println("原始数组:"); + System.out.println(arrayList); + + System.out.println("Collections.max(arrayList):"); + System.out.println(Collections.max(arrayList)); + + System.out.println("Collections.min(arrayList):"); + System.out.println(Collections.min(arrayList)); + + System.out.println("Collections.replaceAll(arrayList, 3, -3):"); + Collections.replaceAll(arrayList, 3, -3); + System.out.println(arrayList); + + System.out.println("Collections.frequency(arrayList, -3):"); + System.out.println(Collections.frequency(arrayList, -3)); + + System.out.println("Collections.indexOfSubList(arrayList, arrayList2):"); + System.out.println(Collections.indexOfSubList(arrayList, arrayList2)); + + System.out.println("Collections.binarySearch(arrayList, 7):"); + // 对List进行二分查找,返回索引,List必须是有序的 + Collections.sort(arrayList); + System.out.println(Collections.binarySearch(arrayList, 7)); +``` + +### 同步控制 + +Collectons提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。 + +我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。 + +**最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。** + +方法如下: + +```java +synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。 +synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。 +synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。 +synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。 +``` + +## Arrays类的常见操作 +1. 排序 : `sort()` +2. 查找 : `binarySearch()` +3. 比较: `equals()` +4. 填充 : `fill()` +5. 转列表: `asList()` +6. 转字符串 : `toString()` +7. + +### 排序 : `sort()` + +```java + // *************排序 sort**************** + int a[] = { 1, 3, 2, 7, 6, 5, 4, 9 }; + // sort(int[] a)方法按照数字顺序排列指定的数组。 + Arrays.sort(a); + System.out.println("Arrays.sort(a):"); + for (int i : a) { + System.out.print(i); + } + // 换行 + System.out.println(); + + // sort(int[] a,int fromIndex,int toIndex)按升序排列数组的指定范围 + int b[] = { 1, 3, 2, 7, 6, 5, 4, 9 }; + Arrays.sort(b, 2, 6); + System.out.println("Arrays.sort(b, 2, 6):"); + for (int i : b) { + System.out.print(i); + } + // 换行 + System.out.println(); + + int c[] = { 1, 3, 2, 7, 6, 5, 4, 9 }; + // parallelSort(int[] a) 按照数字顺序排列指定的数组。同sort方法一样也有按范围的排序 + Arrays.parallelSort(c); + System.out.println("Arrays.parallelSort(c):"); + for (int i : c) { + System.out.print(i); + } + // 换行 + System.out.println(); + + // parallelSort给字符数组排序,sort也可以 + char d[] = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' }; + Arrays.parallelSort(d); + System.out.println("Arrays.parallelSort(d):"); + for (char d2 : d) { + System.out.print(d2); + } + // 换行 + System.out.println(); + +``` + +在做算法面试题的时候,我们还可能会经常遇到对字符串排序的情况,`Arrays.sort()` 对每个字符串的特定位置进行比较,然后按照升序排序。 + +```java +String[] strs = { "abcdehg", "abcdefg", "abcdeag" }; +Arrays.sort(strs); +System.out.println(Arrays.toString(strs));//[abcdeag, abcdefg, abcdehg] +``` + +### 查找 : `binarySearch()` + +```java + // *************查找 binarySearch()**************** + char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' }; + System.out.println("Arrays.binarySearch(e, 'c'):"); + int s = Arrays.binarySearch(e, 'c'); + System.out.println("字符c在数组的位置:" + s); +``` + +### 比较: `equals()` + +```java + // *************比较 equals**************** + char[] e = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' }; + char[] f = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' }; + /* + * 元素数量相同,并且相同位置的元素相同。 另外,如果两个数组引用都是null,则它们被认为是相等的 。 + */ + // 输出true + System.out.println("Arrays.equals(e, f):" + Arrays.equals(e, f)); +``` + +### 填充 : `fill()` + +```java + // *************填充fill(批量初始化)**************** + int[] g = { 1, 2, 3, 3, 3, 3, 6, 6, 6 }; + // 数组中所有元素重新分配值 + Arrays.fill(g, 3); + System.out.println("Arrays.fill(g, 3):"); + // 输出结果:333333333 + for (int i : g) { + System.out.print(i); + } + // 换行 + System.out.println(); + + int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, }; + // 数组中指定范围元素重新分配值 + Arrays.fill(h, 0, 2, 9); + System.out.println("Arrays.fill(h, 0, 2, 9);:"); + // 输出结果:993333666 + for (int i : h) { + System.out.print(i); + } +``` + +### 转列表 `asList()` + +```java + // *************转列表 asList()**************** + /* + * 返回由指定数组支持的固定大小的列表。 + * (将返回的列表更改为“写入数组”。)该方法作为基于数组和基于集合的API之间的桥梁,与Collection.toArray()相结合 。 + * 返回的列表是可序列化的,并实现RandomAccess 。 + * 此方法还提供了一种方便的方式来创建一个初始化为包含几个元素的固定大小的列表如下: + */ + List stooges = Arrays.asList("Larry", "Moe", "Curly"); + System.out.println(stooges); +``` + +### 转字符串 `toString()` + +```java + // *************转字符串 toString()**************** + /* + * 返回指定数组的内容的字符串表示形式。 + */ + char[] k = { 'a', 'f', 'b', 'c', 'e', 'A', 'C', 'B' }; + System.out.println(Arrays.toString(k));// [a, f, b, c, e, A, C, B] +``` + +### 复制 `copyOf()` + +```java + // *************复制 copy**************** + // copyOf 方法实现数组复制,h为数组,6为复制的长度 + int[] h = { 1, 2, 3, 3, 3, 3, 6, 6, 6, }; + int i[] = Arrays.copyOf(h, 6); + System.out.println("Arrays.copyOf(h, 6);:"); + // 输出结果:993333 + for (int j : i) { + System.out.print(j); + } + // 换行 + System.out.println(); + // copyOfRange将指定数组的指定范围复制到新数组中 + int j[] = Arrays.copyOfRange(h, 6, 11); + System.out.println("Arrays.copyOfRange(h, 6, 11):"); + // 输出结果66600(h数组只有9个元素这里是从索引6到索引11复制所以不足的就为0) + for (int j2 : j) { + System.out.print(j2); + } + // 换行 + System.out.println(); +``` + diff --git "a/Java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" "b/Java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" new file mode 100644 index 00000000000..2c8a917f710 --- /dev/null +++ "b/Java/Basis/final\343\200\201static\343\200\201this\343\200\201super.md" @@ -0,0 +1,348 @@ + + +- [final,static,this,super 关键字总结](#finalstaticthissuper-关键字总结) + - [final 关键字](#final-关键字) + - [static 关键字](#static-关键字) + - [this 关键字](#this-关键字) + - [super 关键字](#super-关键字) + - [参考](#参考) +- [static 关键字详解](#static-关键字详解) + - [static 关键字主要有以下四种使用场景](#static-关键字主要有以下四种使用场景) + - [修饰成员变量和成员方法\(常用\)](#修饰成员变量和成员方法常用) + - [静态代码块](#静态代码块) + - [静态内部类](#静态内部类) + - [静态导包](#静态导包) + - [补充内容](#补充内容) + - [静态方法与非静态方法](#静态方法与非静态方法) + - [static{}静态代码块与{}非静态代码块\(构造代码块\)](#static静态代码块与非静态代码块构造代码块) + - [参考](#参考-1) + + + +# final,static,this,super 关键字总结 + +## final 关键字 + +**final关键字主要用在三个地方:变量、方法、类。** + +1. **对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。** + +2. **当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。** + +3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 + +## static 关键字 + +**static 关键字主要有以下四种使用场景:** + +1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()` +2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次. +3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。 +4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。 + +## this 关键字 + +this关键字用于引用类的当前实例。 例如: + +```java +class Manager { + Employees[] employees; + + void manageEmployees() { + int totalEmp = this.employees.length; + System.out.println("Total employees: " + totalEmp); + this.report(); + } + + void report() { } +} +``` + +在上面的示例中,this关键字用于两个地方: + +- this.employees.length:访问类Manager的当前实例的变量。 +- this.report():调用类Manager的当前实例的方法。 + +此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。 + + + +## super 关键字 + +super关键字用于从子类访问父类的变量和方法。 例如: + +```java +public class Super { + protected int number; + + protected showNumber() { + System.out.println("number = " + number); + } +} + +public class Sub extends Super { + void bar() { + super.number = 10; + super.showNumber(); + } +} +``` + +在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber()` 方法。 + +**使用 this 和 super 要注意的问题:** + +- super 调用父类中的其他构造方法时,调用时要放在构造方法的首行!this 调用本类中的其他构造方法时,也要放在首行。 +- this、super不能用在static方法中。 + +**简单解释一下:** + +被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。 + + + +## 参考 + +- https://www.codejava.net/java-core/the-java-language/java-keywords +- https://blog.csdn.net/u013393958/article/details/79881037 + +# static 关键字详解 + +## static 关键字主要有以下四种使用场景 + +1. 修饰成员变量和成员方法 +2. 静态代码块 +3. 修饰类(只能修饰内部类) +4. 静态导包(用来导入类中的静态资源,1.5之后的新特性) + +### 修饰成员变量和成员方法(常用) + +被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。 + +方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。 + + HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 + + + +调用格式: + +- 类名.静态变量名 +- 类名.静态方法名() + +如果变量或者方法被 private 则代表该属性或者该方法只能在类的内部被访问而不能在类的外部被访问。 + +测试方法: + +```java +public class StaticBean { + + String name; + 静态变量 + static int age; + + public StaticBean(String name) { + this.name = name; + } + 静态方法 + static void SayHello() { + System.out.println(Hello i am java); + } + @Override + public String toString() { + return StaticBean{ + + name=' + name + ''' + age + age + + '}'; + } +} +``` + +```java +public class StaticDemo { + + public static void main(String[] args) { + StaticBean staticBean = new StaticBean(1); + StaticBean staticBean2 = new StaticBean(2); + StaticBean staticBean3 = new StaticBean(3); + StaticBean staticBean4 = new StaticBean(4); + StaticBean.age = 33; + StaticBean{name='1'age33} StaticBean{name='2'age33} StaticBean{name='3'age33} StaticBean{name='4'age33} + System.out.println(staticBean+ +staticBean2+ +staticBean3+ +staticBean4); + StaticBean.SayHello();Hello i am java + } + +} +``` + + +### 静态代码块 + +静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。 该类不管创建多少对象,静态代码块只执行一次. + +静态代码块的格式是 + +``` +static { +语句体; +} +``` + + +一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/88531075.jpg) + +静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问. + + +### 静态内部类 + +静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着: + +1. 它的创建是不需要依赖外围类的创建。 +2. 它不能使用任何外围类的非static成员变量和方法。 + + +Example(静态内部类实现单例模式) + +```java +public class Singleton { + + 声明为 private 避免调用默认构造方法创建对象 + private Singleton() { + } + + 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问 + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getUniqueInstance() { + return SingletonHolder.INSTANCE; + } +} +``` + +当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。 + +这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。 + +### 静态导包 + +格式为:import static + +这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法 + +```java + + + Math. --- 将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用 + 如果只想导入单一某个静态方法,只需要将换成对应的方法名即可 + +import static java.lang.Math.; + + 换成import static java.lang.Math.max;具有一样的效果 + +public class Demo { + public static void main(String[] args) { + + int max = max(1,2); + System.out.println(max); + } +} + +``` + + +## 补充内容 + +### 静态方法与非静态方法 + +静态方法属于类本身,非静态方法属于从该类生成的每个对象。 如果您的方法执行的操作不依赖于其类的各个变量和方法,请将其设置为静态(这将使程序的占用空间更小)。 否则,它应该是非静态的。 + +Example + +```java +class Foo { + int i; + public Foo(int i) { + this.i = i; + } + + public static String method1() { + return An example string that doesn't depend on i (an instance variable); + + } + + public int method2() { + return this.i + 1; Depends on i + } + +} +``` +你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行:`Foo bar = new Foo(1);bar.method2();` + +总结: + +- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 +- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制 + +### static{}静态代码块与{}非静态代码块(构造代码块) + +相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。 + +不同点: 静态代码块在非静态代码块之前执行(静态代码块—非静态代码块—构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。 + +一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的. + +Example + +```java +public class Test { + public Test() { + System.out.print(默认构造方法!--); + } + + 非静态代码块 + { + System.out.print(非静态代码块!--); + } + 静态代码块 + static { + System.out.print(静态代码块!--); + } + + public static void test() { + System.out.print(静态方法中的内容! --); + { + System.out.print(静态方法中的代码块!--); + } + + } + public static void main(String[] args) { + + Test test = new Test(); + Test.test();静态代码块!--静态方法中的内容! --静态方法中的代码块!-- + } +``` + +当执行 `Test.test();` 时输出: + +``` +静态代码块!--静态方法中的内容! --静态方法中的代码块!-- +``` + +当执行 `Test test = new Test();` 时输出: + +``` +静态代码块!--非静态代码块!--默认构造方法!-- +``` + + +非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。 + +### 参考 + +- httpsblog.csdn.netchen13579867831articledetails78995480 +- httpwww.cnblogs.comchenssyp3388487.html +- httpwww.cnblogs.comQian123p5713440.html diff --git "a/Java\347\233\270\345\205\263/HashMap.md" b/Java/HashMap.md similarity index 96% rename from "Java\347\233\270\345\205\263/HashMap.md" rename to Java/HashMap.md index 19b77c50828..45fad50cdb3 100644 --- "a/Java\347\233\270\345\205\263/HashMap.md" +++ b/Java/HashMap.md @@ -22,7 +22,7 @@ JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体 ## 底层数据结构分析 ### JDK1.8之前 -JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** **所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** @@ -90,15 +90,17 @@ public class HashMap extends AbstractMap implements Map, Cloneabl transient int modCount; // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 int threshold; - // 填充因子 + // 加载因子 final float loadFactor; } ``` - **loadFactor加载因子** - loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,load Factor越小,也就是趋近于0, + loadFactor加载因子是控制数组存放数据的疏密程度,loadFactor越趋近于1,那么 数组中存放的数据(entry)也就越多,也就越密,也就是会让链表的长度增加,loadFactor越小,也就是趋近于0,数组中存放的数据(entry)也就越少,也就越稀疏。 - **loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值**。   + **loadFactor太大导致查找元素效率低,太小导致数组的利用率低,存放的数据会很分散。loadFactor的默认值为0.75f是官方给出的一个比较好的临界值**。 + + 给定的默认容量为 16,负载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容,而扩容这个过程涉及到 rehash、复制数据等操作,所以非常消耗性能。 - **threshold** @@ -171,23 +173,23 @@ static final class TreeNode extends LinkedHashMap.Entry { ![四个构造方法](https://user-gold-cdn.xitu.io/2018/3/20/162410d912a2e0e1?w=336&h=90&f=jpeg&s=26744) ```java // 默认构造函数。 - public More ...HashMap() { + public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } // 包含另一个“Map”的构造函数 - public More ...HashMap(Map m) { + public HashMap(Map m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false);//下面会分析到这个方法 } // 指定“容量大小”的构造函数 - public More ...HashMap(int initialCapacity) { + public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } // 指定“容量大小”和“加载因子”的构造函数 - public More ...HashMap(int initialCapacity, float loadFactor) { + public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) @@ -399,7 +401,7 @@ final Node[] resize() { else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { - signifies using defaults + // signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } diff --git "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" similarity index 93% rename from "Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" rename to "Java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" index da4a7e32e6f..ced017ab47b 100644 --- "a/Java\347\233\270\345\205\263/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/Java/J2EE\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -61,16 +61,19 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期 - java.lang.String getServletInfo() - ServletConfig getServletConfig() -**生命周期:** **Web容器加载Servlet并将其实例化后,Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化;请求到达时调用Servlet的**service()方法**,service()方法会根据需要调用与请求对应的**doGet或doPost**等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的**destroy()方法**。**init方法和destory方法只会执行一次,service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。 +**生命周期:** **Web容器加载Servlet并将其实例化后,Servlet生命周期开始**,容器运行其**init()方法**进行Servlet的初始化;请求到达时调用Servlet的**service()方法**,service()方法会根据需要调用与请求对应的**doGet或doPost**等方法;当服务器关闭或项目被卸载时服务器会将Servlet实例销毁,此时会调用Servlet的**destroy()方法**。**init方法和destroy方法只会执行一次,service方法客户端每次请求Servlet都会执行**。Servlet中有时会用到一些需要初始化与销毁的资源,因此可以把初始化资源的代码放入init方法中,销毁资源的代码放入destroy方法中,这样就不需要每次处理客户端的请求都要初始化与销毁资源。 参考:《javaweb整合开发王者归来》P81 ## get和post请求的区别 + +> 网上也有文章说:get和post请求实际上是没有区别,大家可以自行查询相关文章(参考文章:[https://www.cnblogs.com/logsharing/p/8448446.html](https://www.cnblogs.com/logsharing/p/8448446.html),知乎对应的问题链接:[get和post区别?](https://www.zhihu.com/question/28586791))!我下面给出的只是一种常见的答案。 + ①get请求用来从服务器上获得资源,而post是用来向服务器提交数据; ②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL; -③get传输的数据要受到URL长度限制(1024字节即256个字符);而post可以传输大量的数据,上传文件通常要使用post方式; +③get传输的数据要受到URL长度限制(最大长度是 2048 个字符);而post可以传输大量的数据,上传文件通常要使用post方式; ④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据,那么可以使用get;对于敏感数据还是应用使用post; @@ -78,6 +81,10 @@ Servlet接口定义了5个方法,其中**前三个方法与Servlet生命周期 补充:GET方式提交表单的典型应用是搜索引擎。GET方式就是被设计为查询用的。 +还有另外一种回答。推荐大家看一下: + +- https://www.zhihu.com/question/28586791 +- https://mp.weixin.qq.com/s?__biz=MzI3NzIzMzg3Mw==&mid=100000054&idx=1&sn=71f6c214f3833d9ca20b9f7dcd9d33e4#rd ## 什么情况下调用doGet()和doPost() Form标签里的method的属性为get时调用doGet(),为post时调用doPost()。 @@ -91,7 +98,7 @@ Form标签里的method的属性为get时调用doGet(),为post时调用doPost() ```java request.getRequestDispatcher("login_success.jsp").forward(request, response); ``` -**重定向(Redirect)** 是利用服务器返回的状态吗来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过HttpServletRequestResponse的setStatus(int status)方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。 +**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 `HttpServletResponse` 的 `setStatus(int status)` 方法设置状态码。如果服务器返回301或者302,则浏览器会到新的网址重新请求该资源。 1. **从地址栏显示来说** @@ -116,9 +123,9 @@ redirect:低. ## 自动刷新(Refresh) 自动刷新不仅可以实现一段时间之后自动跳转到另一个页面,还可以实现一段时间之后自动刷新本页面。Servlet中通过HttpServletResponse对象设置Header属性实现自动刷新例如: ```java -Response.setHeader("Refresh","1000;URL=http://localhost:8080/servlet/example.htm"); +Response.setHeader("Refresh","5;URL=http://localhost:8080/servlet/example.htm"); ``` -其中1000为时间,单位为毫秒。URL指定就是要跳转的页面(如果设置自己的路径,就会实现没过一秒自动刷新本页面一次) +其中5为时间,单位为秒。URL指定就是要跳转的页面(如果设置自己的路径,就会实现每过5秒自动刷新本页面一次) ## Servlet与线程安全 diff --git "a/Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" "b/Java/Java IO\344\270\216NIO.md" similarity index 100% rename from "Java\347\233\270\345\205\263/Java IO\344\270\216NIO.md" rename to "Java/Java IO\344\270\216NIO.md" diff --git "a/Java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" new file mode 100644 index 00000000000..cf6255b0265 --- /dev/null +++ "b/Java/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -0,0 +1,475 @@ + + + + +- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) + - [面向过程](#面向过程) + - [面向对象](#面向对象) +- [2. Java 语言有哪些特点](#2-java-语言有哪些特点) +- [3. 关于 JVM JDK 和 JRE 最详细通俗的解答](#3-关于-jvm-jdk-和-jre-最详细通俗的解答) + - [JVM](#jvm) + - [JDK 和 JRE](#jdk-和-jre) +- [4. Oracle JDK 和 OpenJDK 的对比](#4-oracle-jdk-和-openjdk-的对比) +- [5. Java和C++的区别](#5-java和c的区别) +- [6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同](#6-什么是-java-程序的主类-应用程序和小程序的主类有何不同) +- [7. Java 应用程序与小程序之间有那些差别](#7-java-应用程序与小程序之间有那些差别) +- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) +- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) +- [10. 重载和重写的区别](#10-重载和重写的区别) +- [11. Java 面向对象编程三大特性: 封装 继承 多态](#11-java-面向对象编程三大特性-封装-继承-多态) + - [封装](#封装) + - [继承](#继承) + - [多态](#多态) +- [12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的](#12-string-stringbuffer-和-stringbuilder-的区别是什么-string-为什么是不可变的) +- [13. 自动装箱与拆箱](#13-自动装箱与拆箱) +- [14. 在一个静态方法内调用一个非静态成员为什么是非法的](#14-在一个静态方法内调用一个非静态成员为什么是非法的) +- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) +- [16. import java和javax有什么区别](#16-import-java和javax有什么区别) +- [17. 接口和抽象类的区别是什么](#17-接口和抽象类的区别是什么) +- [18. 成员变量与局部变量的区别有那些](#18-成员变量与局部变量的区别有那些) +- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符对象实体与对象引用有何不同) +- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值返回值在类的方法里的作用是什么) +- [21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么?](#21-一个类的构造方法的作用是什么-若一个类没有声明构造方法该程序能正确执行吗-为什么) +- [22. 构造方法有哪些特性](#22-构造方法有哪些特性) +- [23. 静态方法和实例方法有何不同](#23-静态方法和实例方法有何不同) +- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等两者有什么不同) +- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法其目的是) +- [26. == 与 equals\(重要\)](#26--与-equals重要) +- [27. hashCode 与 equals \(重要\)](#27-hashcode-与-equals-重要) + - [hashCode()介绍](#hashcode()介绍) + - [为什么要有 hashCode](#为什么要有-hashcode) + - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) +- [28. 为什么Java中只有值传递](#28-为什么java中只有值传递) +- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么](#29-简述线程程序进程的基本概念以及他们之间关系是什么) +- [30. 线程有哪些基本状态?](#30-线程有哪些基本状态) +- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) +- [32 Java 中的异常处理](#32-java-中的异常处理) + - [Java异常类层次结构图](#java异常类层次结构图) + - [Throwable类常用方法](#throwable类常用方法) + - [异常处理总结](#异常处理总结) +- [33 Java序列化中如果有些字段不想进行序列化 怎么办](#33-java序列化中如果有些字段不想进行序列化-怎么办) +- [34 获取用键盘输入常用的的两种方法](#34-获取用键盘输入常用的的两种方法) +- [参考](#参考) + + + + + +## 1. 面向对象和面向过程的区别 + +### 面向过程 + +**优点:** 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 + +**缺点:** 没有面向对象易维护、易复用、易扩展 + +### 面向对象 + +**优点:** 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 + +**缺点:** 性能比面向过程低 + +## 2. Java 语言有哪些特点 + +1. 简单易学; +2. 面向对象(封装,继承,多态); +3. 平台无关性( Java 虚拟机实现平台无关性); +4. 可靠性; +5. 安全性; +6. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持); +7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); +8. 编译与解释并存; + +## 3. 关于 JVM JDK 和 JRE 最详细通俗的解答 + +### JVM + +Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。 + +**什么是字节码?采用字节码的好处是什么?** + +> 在 Java 中,JVM可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 + +**Java 程序从源代码到运行一般有下面3步:** + +![Java程序运行过程](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E8%BF%90%E8%A1%8C%E8%BF%87%E7%A8%8B.png) + +我们需要格外注意的是 .class->机器码 这一步。在这一步 jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,也就是所谓的热点代码,所以后面引进了 JIT 编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。 + +> HotSpot采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是JIT所需要编译的部分。JVM会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9引入了一种新的编译模式AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了JIT预热等各方面的开销。JDK支持分层编译和AOT协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。 + +总结:Java虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。 + +### JDK 和 JRE + +JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。 + +JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。 + +如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。 + +## 4. Oracle JDK 和 OpenJDK 的对比 + +可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么Oracle和OpenJDK之间是否存在重大差异?下面通过我通过我收集到一些资料对你解答这个被很多人忽视的问题。 + +对于Java 7,没什么关键的地方。OpenJDK项目主要基于Sun捐赠的HotSpot源代码。此外,OpenJDK被选为Java 7的参考实现,由Oracle工程师维护。关于JVM,JDK,JRE和OpenJDK之间的区别,Oracle博客帖子在2012年有一个更详细的答案: + +> 问:OpenJDK存储库中的源代码与用于构建Oracle JDK的代码之间有什么区别? +> +> 答:非常接近 - 我们的Oracle JDK版本构建过程基于OpenJDK 7构建,只添加了几个部分,例如部署代码,其中包括Oracle的Java插件和Java WebStart的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源Oracle JDK的所有部分,除了我们考虑商业功能的部分。 + +总结: + +1. Oracle JDK版本将每三年发布一次,而OpenJDK版本每三个月发布一次; +2. OpenJDK 是一个参考模型并且是完全开源的,而Oracle JDK是OpenJDK的一个实现,并不是完全开源的; +3. Oracle JDK 比 OpenJDK 更稳定。OpenJDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,我建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用OpenJDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题; +4. 在响应性和JVM性能方面,Oracle JDK与OpenJDK相比提供了更好的性能; +5. Oracle JDK不会为即将发布的版本提供长期支持,用户每次都必须通过更新到最新版本获得支持来获取最新版本; +6. Oracle JDK根据二进制代码许可协议获得许可,而OpenJDK根据GPL v2许可获得许可。 + +## 5. Java和C++的区别 + +我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来! + +- 都是面向对象的语言,都支持封装、继承和多态 +- Java 不提供指针来直接访问内存,程序内存更加安全 +- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。 +- Java 有自动内存管理机制,不需要程序员手动释放无用内存 + + +## 6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同 + +一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。 + +## 7. Java 应用程序与小程序之间有那些差别 + +简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。 + +## 8. 字符型常量和字符串常量的区别 + +1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符 +2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置) +3. 占内存大小 字符常量只占2个字节 字符串常量占若干个字节(至少一个字符结束标志) (**注意: char在Java中占两个字节**) + +> java编程思想第四版:2.2.2节 +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-15/86735519.jpg) + +## 9. 构造器 Constructor 是否可被 override + +在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override(重写),但是可以 overload(重载),所以你可以看到一个类中有多个构造函数的情况。 + +## 10. 重载和重写的区别 + +**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    + +**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 + +## 11. Java 面向对象编程三大特性: 封装 继承 多态 + +### 封装 + +封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 + + +### 继承 +继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。 + +**关于继承如下 3 点请记住:** + +1. 子类拥有父类非 private 的属性和方法。 +2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 +3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 + +### 多态 + +所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 + +在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 + +## 12. String StringBuffer 和 StringBuilder 的区别是什么 String 为什么是不可变的 + +**可变性** +  + +简单的来说:String 类中使用 final 关键字字符数组保存字符串,`private final char value[]`,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。 + +StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。 + +AbstractStringBuilder.java + +```java +abstract class AbstractStringBuilder implements Appendable, CharSequence { + char[] value; + int count; + AbstractStringBuilder() { + } + AbstractStringBuilder(int capacity) { + value = new char[capacity]; + } +``` + + +**线程安全性** + +String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 +   + +**性能** + +每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 + +**对于三者使用的总结:** +1. 操作少量的数据 = String +2. 单线程操作字符串缓冲区下操作大量数据 = StringBuilder +3. 多线程操作字符串缓冲区下操作大量数据 = StringBuffer + +## 13. 自动装箱与拆箱 +**装箱**:将基本类型用它们对应的引用类型包装起来; + +**拆箱**:将包装类型转换为基本数据类型; + +## 14. 在一个静态方法内调用一个非静态成员为什么是非法的 + +由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 + +## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用 + Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 +  +## 16. import java和javax有什么区别 + +刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 + +所以,实际上java和javax没有区别。这都是一个名字。 + +## 17. 接口和抽象类的区别是什么 + +1. 接口的方法默认是 public,所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现),抽象类可以有非抽象的方法 +2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 +3. 一个类可以实现多个接口,但最多只能实现一个抽象类 +4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 +5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 + +备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,必须重写,不然会报错。(详见issue:[https://github.com/Snailclimb/JavaGuide/issues/146](https://github.com/Snailclimb/JavaGuide/issues/146)) + +## 18. 成员变量与局部变量的区别有那些 + +1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰; +2. 从变量在内存中的存储方式来看:如果成员变量是使用`static`修饰的,那么这个成员变量是属于类的,如果没有使用使用`static`修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存 +3. 从变量在内存中的生存时间上看:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 +4. 成员变量如果没有被赋初值:则会自动以类型的默认值而赋值(一种情况例外被 final 修饰的成员变量也必须显示地赋值);而局部变量则不会自动赋值。 + +## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? + +new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。 + +## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么? + +方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! + +## 21. 一个类的构造方法的作用是什么 若一个类没有声明构造方法,该程序能正确执行吗 ?为什么? + +主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 + +## 22. 构造方法有哪些特性 + +1. 名字与类名相同; +2. 没有返回值,但不能用void声明构造函数; +3. 生成类的对象时自动执行,无需调用。 + +## 23. 静态方法和实例方法有何不同 + +1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 + +2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制. + +## 24. 对象的相等与指向他们的引用相等,两者有什么不同? + +对象的相等,比的是内存中存放的内容是否相等。而引用相等,比较的是他们指向的内存地址是否相等。 + +## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? + +帮助子类做初始化工作。 + +## 26. == 与 equals(重要) + +**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) + +**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: +- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。 +- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 + + +**举个例子:** + +```java +public class test1 { + public static void main(String[] args) { + String a = new String("ab"); // a 为一个引用 + String b = new String("ab"); // b为另一个引用,对象的内容一样 + String aa = "ab"; // 放在常量池中 + String bb = "ab"; // 从常量池中查找 + if (aa == bb) // true + System.out.println("aa==bb"); + if (a == b) // false,非同一对象 + System.out.println("a==b"); + if (a.equals(b)) // true + System.out.println("aEQb"); + if (42 == 42.0) { // true + System.out.println("true"); + } + } +} +``` + +**说明:** +- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。 +- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 + + + +## 27. hashCode 与 equals (重要) + +面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” + +### hashCode()介绍 +hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 + +散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) + +### 为什么要有 hashCode + + +**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** + +当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head first java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 + + + +### hashCode()与equals()的相关规定 + +1. 如果两个对象相等,则hashcode一定也是相同的 +2. 两个对象相等,对两个对象分别调用equals方法都返回true +3. 两个对象有相同的hashcode值,它们也不一定是相等的 +4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖** +5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) + + +## 28. 为什么Java中只有值传递 + + [为什么Java中只有值传递?](https://github.com/Snailclimb/JavaGuide/blob/master/EssentialContentForInterview/MostCommonJavaInterviewQuestions/%E7%AC%AC%E4%B8%80%E5%91%A8%EF%BC%882018-8-7%EF%BC%89.md) + + +## 29. 简述线程,程序,进程的基本概念.以及他们之间关系是什么? + +**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 + +**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。 + +**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 +线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。 + +## 30. 线程有哪些基本状态? + +Java 线程在运行的生命周期中的指定时刻只可能处于下面6种不同状态的其中一个状态(图源《Java 并发编程艺术》4.1.4节)。 + +![Java线程的状态](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%8A%B6%E6%80%81.png) + +线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示(图源《Java 并发编程艺术》4.1.4节): + +![Java线程状态变迁](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-29/Java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E5%8F%98%E8%BF%81.png) + + + +由上图可以看出: + +线程创建之后它将处于 **NEW(新建)** 状态,调用 `start()` 方法后开始运行,线程这时候处于 **READY(可运行)** 状态。可运行状态的线程获得了 cpu 时间片(timeslice)后就处于 **RUNNING(运行)** 状态。 + +> 操作系统隐藏 Java虚拟机(JVM)中的 RUNNABLE 和 RUNNING 状态,它只能看到 RUNNABLE 状态(图源:[HowToDoInJava](https://howtodoinjava.com/):[Java Thread Life Cycle and Thread States](https://howtodoinjava.com/java/multi-threading/java-thread-life-cycle-and-thread-states/)),所以 Java 系统一般将这两个状态统称为 **RUNNABLE(运行中)** 状态 。 + +![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png) + +当线程执行 `wait()`方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 **TIME_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 **BLOCKED(阻塞)** 状态。线程在执行 Runnable 的` run() `方法之后将会进入到 **TERMINATED(终止)** 状态。 + +## 31 关于 final 关键字的一些总结 + +final关键字主要用在三个地方:变量、方法、类。 + +1. 对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 +2. 当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 +3. 使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。 + +## 32 Java 中的异常处理 + +### Java异常类层次结构图 + +![Java异常类层次结构图](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/Exception.png) + 在 Java 中,所有的异常都有一个共同的祖先java.lang包中的 **Throwable类**。Throwable: 有两个重要的子类:**Exception(异常)** 和 **Error(错误)** ,二者都是 Java 异常处理的重要子类,各自都包含大量子类。 + +**Error(错误):是程序无法处理的错误**,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。 + +这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。 + +**Exception(异常):是程序本身可以处理的异常**。Exception 类有一个重要的子类 **RuntimeException**。RuntimeException 异常由Java虚拟机抛出。**NullPointerException**(要访问的变量没有引用任何对象时,抛出该异常)、**ArithmeticException**(算术运算异常,一个整数除以0时,抛出该异常)和 **ArrayIndexOutOfBoundsException** (下标越界异常)。 + +**注意:异常和错误的区别:异常能被程序本身可以处理,错误是无法处理。** + +### Throwable类常用方法 + +- **public string getMessage()**:返回异常发生时的详细信息 +- **public string toString()**:返回异常发生时的简要描述 +- **public string getLocalizedMessage()**:返回异常对象的本地化信息。使用Throwable的子类覆盖这个方法,可以声称本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同 +- **public void printStackTrace()**:在控制台上打印Throwable对象封装的异常信息 + +### 异常处理总结 + +- **try 块:**用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 +- **catch 块:**用于处理try捕获到的异常。 +- **finally 块:**无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。 + +**在以下4种特殊情况下,finally块不会被执行:** + +1. 在finally语句块第一行发生了异常。 因为在其他行,finally块还是会得到执行 +2. 在前面的代码中用了System.exit(int)已退出程序。 exit是带参函数 ;若该语句在异常语句之后,finally会执行 +3. 程序所在的线程死亡。 +4. 关闭CPU。 + +下面这部分内容来自issue:。 + +**关于返回值:** + +如果try语句里有return,返回的是try语句块中变量值。 +详细执行过程如下: + +1. 如果有返回值,就把返回值保存到局部变量中; +2. 执行jsr指令跳到finally语句里执行; +3. 执行完finally语句后,返回之前保存在局部变量表里的值。 +4. 如果try,finally语句里均有return,忽略try的return,而使用finally的return. + +## 33 Java序列化中如果有些字段不想进行序列化 怎么办 + +对于不想进行序列化的变量,使用transient关键字修饰。 + +transient关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。 + +## 34 获取用键盘输入常用的的两种方法 + +方法1:通过 Scanner + +```java +Scanner input = new Scanner(System.in); +String s = input.nextLine(); +input.close(); +``` + +方法2:通过 BufferedReader + +```java +BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); +String s = input.readLine(); +``` + +## 参考 + +- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre +- https://www.educba.com/oracle-vs-openjdk/ +- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top diff --git "a/Java/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" "b/Java/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" new file mode 100644 index 00000000000..9be88bc89c3 --- /dev/null +++ "b/Java/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" @@ -0,0 +1,59 @@ + +下面是按jvm虚拟机知识点分章节总结的一些jvm学习与面试相关的一些东西。一般作为Java程序员在面试的时候一般会问的大多就是**Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理**这些问题了。这些内容参考周的《深入理解Java虚拟机》中第二章和第三章就足够了对应下面的[深入理解虚拟机之Java内存区域:](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)和[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)这两篇文章。 + + +> ### 常见面试题 + +[深入理解虚拟机之Java内存区域](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484960&idx=1&sn=ff3739fe849030178346bef28a4556c3&chksm=cea249ebf9d5c0fdbde7c86155d0d7ac8925153742aff472bcb79e5e9d400534a855bad38375&token=1082669959&lang=zh_CN#rd) + +1. 介绍下Java内存区域(运行时数据区)。 + +2. 对象的访问定位的两种方式。 + + +[深入理解虚拟机之垃圾回收](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484959&idx=1&sn=9ac740edba59981b7c89482043776280&chksm=cea249d4f9d5c0c21703382510a47d4bb387932bd814ac891fd214b92cead5d2cf0ee2dff797&token=1082669959&lang=zh_CN#rd) + +1. 如何判断对象是否死亡(两种方法)。 + +2. 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 + +3. 垃圾收集有哪些算法,各自的特点? + +4. HotSpot为什么要分为新生代和老年代? + +5. 常见的垃圾回收器有那些? + +6. 介绍一下CMS,G1收集器。 + +7. Minor Gc和Full GC 有什么不同呢? + + + + [虚拟机性能监控和故障处理工具](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484957&idx=1&sn=713ed6003d23ef883ded14cb43e9ebb7&chksm=cea249d6f9d5c0c0ce0854a03f0d02fcacc8a46e29c2fd4f085a375b00e1cd1b632937a9895e&token=1082669959&lang=zh_CN#rd) + +1. JVM调优的常见命令行工具有哪些? + + [深入理解虚拟机之类文件结构](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484956&idx=1&sn=05f46ccacacdbce7c43de594d3fe93db&chksm=cea249d7f9d5c0c1ef6d29b0fbbf0701acd28490deb0974ae71b4d23ae793bec0b0993a4c829&token=1082669959&lang=zh_CN#rd) + +1. 简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?) + +[深入理解虚拟机之虚拟机字节码执行引擎](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484952&idx=1&sn=d0ec9443600dc5b2a81782b7ae0691d5&chksm=cea249d3f9d5c0c50642f1829fd6fe9e35d155bbbb6718611330c7c46c7158279275b533181e&token=1082669959&lang=zh_CN#rd) + +1. 简单说说类加载过程,里面执行了哪些操作? + +2. 对类加载器有了解吗? + +3. 什么是双亲委派模型? + +4. 双亲委派模型的工作过程以及使用它的好处。 + + + + + +> ### 推荐阅读 + + [《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) (非常不错的文章) + [全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461) + + diff --git "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" "b/Java/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" similarity index 94% rename from "Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" rename to "Java/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" index d18c68e0020..cb0bd1fe0e3 100644 --- "a/Java\347\233\270\345\205\263/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" +++ "b/Java/Java\351\233\206\345\220\210\346\241\206\346\236\266\345\270\270\350\247\201\351\235\242\350\257\225\351\242\230\346\200\273\347\273\223.md" @@ -40,7 +40,7 @@ ## Arraylist 与 LinkedList 区别 -Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList底层使用的是双向循环链表数据结构(插入,删除效率特别高)。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。 +Arraylist底层使用的是数组(存读数据效率高,插入删除特定位置效率低),LinkedList 底层使用的是双向链表数据结构(插入,删除效率特别高)(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读JDK1.7-LinkedList循环链表优化。学过数据结构这门课后我们就知道采用链表存储,插入,删除元素时间复杂度不受元素位置的影响,都是近似O(1)而数组为近似O(n),因此当数据特别多,而且经常需要插入删除元素时建议选用LinkedList.一般程序只用Arraylist就够用了,因为一般数据量都不会蛮大,Arraylist是使用最多的集合类。 ## ArrayList 与 Vector 区别 Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对象、但是一个线程访问Vector @@ -76,7 +76,8 @@ Hashtable和HashMap有几个主要的不同:线程安全以及速度。仅在 **==与equals的区别** 1. ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同 -2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较3.==指引用是否相同 equals()指的是值是否相同 +2. ==是指对内存地址进行比较 equals()是对字符串的内容进行比较 +3. ==指引用是否相同 equals()指的是值是否相同 ## comparable 和 comparator的区别 - comparable接口实际上是出自java.lang包 它有一个 compareTo(Object obj)方法用来排序 diff --git "a/Java\347\233\270\345\205\263/LinkedList.md" b/Java/LinkedList.md similarity index 98% rename from "Java\347\233\270\345\205\263/LinkedList.md" rename to Java/LinkedList.md index bf5e55ab8e4..983c1fae0d0 100644 --- "a/Java\347\233\270\345\205\263/LinkedList.md" +++ b/Java/LinkedList.md @@ -28,8 +28,8 @@ List list=Collections.synchronizedList(new LinkedList(...)); ```java private static class Node { E item;//节点值 - Node next;//前驱节点 - Node prev;//后继节点 + Node next;//后继节点 + Node prev;//前驱节点 Node(Node prev, E element, Node next) { this.item = element; @@ -186,7 +186,7 @@ public void addLast(E e) { } ``` ### 根据位置取数据的方法 -**get(int index):**:根据指定索引返回数据 +**get(int index):** 根据指定索引返回数据 ```java public E get(int index) { //检查index范围是否在size之内 @@ -220,7 +220,7 @@ public E peekFirst() { getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中**getFirst()** 和**element()** 方法将会在链表为空时,抛出异常 -element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException +element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException **获取尾节点(index=-1)数据方法:** ```java public E getLast() { @@ -288,7 +288,7 @@ public int lastIndexOf(Object o) { return indexOf(o) != -1; } ``` -###删除方法 +### 删除方法 **remove()** ,**removeFirst(),pop():** 删除头节点 ``` public E pop() { @@ -359,7 +359,7 @@ E unlink(Node x) { //删除前驱指针 if (prev == null) { - first = next;如果删除的节点是头节点,令头节点指向该节点的后继节点 + first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点 } else { prev.next = next;//将前驱节点的后继节点指向后继节点 x.prev = null; diff --git a/Java/Multithread/AQS.md b/Java/Multithread/AQS.md new file mode 100644 index 00000000000..f405db1f17d --- /dev/null +++ b/Java/Multithread/AQS.md @@ -0,0 +1,476 @@ + +**目录:** + + +- [1 AQS 简单介绍](#1-aqs-简单介绍) +- [2 AQS 原理](#2-aqs-原理) + - [2.1 AQS 原理概览](#21-aqs-原理概览) + - [2.2 AQS 对资源的共享方式](#22-aqs-对资源的共享方式) + - [2.3 AQS底层使用了模板方法模式](#23-aqs底层使用了模板方法模式) +- [3 Semaphore\(信号量\)-允许多个线程同时访问](#3-semaphore信号量-允许多个线程同时访问) +- [4 CountDownLatch (倒计时器)](#4-countdownlatch-倒计时器) + - [4.1 CountDownLatch 的三种典型用法](#41-countdownlatch-的三种典型用法) + - [4.2 CountDownLatch 的使用示例](#42-countdownlatch-的使用示例) + - [4.3 CountDownLatch 的不足](#43-countdownlatch-的不足) + - [4.4 CountDownLatch相常见面试题:](#44-countdownlatch相常见面试题) +- [5 CyclicBarrier\(循环栅栏\)](#5-cyclicbarrier循环栅栏) + - [5.1 CyclicBarrier 的应用场景](#51-cyclicbarrier-的应用场景) + - [5.2 CyclicBarrier 的使用示例](#52-cyclicbarrier-的使用示例) + - [5.3 CyclicBarrier和CountDownLatch的区别](#53-cyclicbarrier和countdownlatch的区别) +- [6 ReentrantLock 和 ReentrantReadWriteLock](#6-reentrantlock-和-reentrantreadwritelock) + + + +> 常见问题:AQS原理?;CountDownLatch和CyclicBarrier了解吗,两者的区别是什么?用过Semaphore吗? + +**本节思维导图:** + +![并发编程面试必备:AQS 原理以及 AQS 同步组件总结](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-31/61115865.jpg) + + +### 1 AQS 简单介绍 +AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 + +![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS.png) + +AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 + +### 2 AQS 原理 + +> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要加入自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。 + +下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。 + +#### 2.1 AQS 原理概览 + +**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** + +> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 + +看个AQS(AbstractQueuedSynchronizer)原理图: + + +![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/CLH.png) + +AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 + +```java +private volatile int state;//共享变量,使用volatile修饰保证线程可见性 +``` + +状态信息通过protected类型的getState,setState,compareAndSetState进行操作 + +```java + +//返回同步状态的当前值 +protected final int getState() { + return state; +} + // 设置同步状态的值 +protected final void setState(int newState) { + state = newState; +} +//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) +protected final boolean compareAndSetState(int expect, int update) { + return unsafe.compareAndSwapInt(this, stateOffset, expect, update); +} +``` + +#### 2.2 AQS 对资源的共享方式 + +**AQS定义两种资源共享方式** + +- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: + - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 + - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 +- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 + +ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 + +不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在上层已经帮我们实现好了。 + +#### 2.3 AQS底层使用了模板方法模式 + +同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): + +1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) +2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 + +这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用,下面简单的给大家介绍一下模板方法模式,模板方法模式是一个很容易理解的设计模式之一。 + +> 模板方法模式是基于”继承“的,主要是为了在不改变模板结构的前提下在子类中重新定义模板中的内容以实现复用代码。举个很简单的例子假如我们要去一个地方的步骤是:购票`buyTicket()`->安检`securityCheck()`->乘坐某某工具回家`ride()`->到达目的地`arrive()`。我们可能乘坐不同的交通工具回家比如飞机或者火车,所以除了`ride()`方法,其他方法的实现几乎相同。我们可以定义一个包含了这些方法的抽象类,然后用户根据自己的需要继承该抽象类然后修改 `ride()`方法。 + +**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:** + +```java +isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 +tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 +tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 +tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 +tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 + +``` + +默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 + +以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 + +再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 + +一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 + +推荐两篇 AQS 原理和相关源码分析的文章: + +- http://www.cnblogs.com/waterystone/p/4920797.html +- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html + + + +### 3 Semaphore(信号量)-允许多个线程同时访问 + +**synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。**示例代码如下: + +```java +/** + * + * @author Snailclimb + * @date 2018年9月30日 + * @Description: 需要一次性拿一个许可的情况 + */ +public class SemaphoreExample1 { + // 请求的数量 + private static final int threadCount = 550; + + public static void main(String[] args) throws InterruptedException { + // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) + ExecutorService threadPool = Executors.newFixedThreadPool(300); + // 一次只能允许执行的线程数量。 + final Semaphore semaphore = new Semaphore(20); + + for (int i = 0; i < threadCount; i++) { + final int threadnum = i; + threadPool.execute(() -> {// Lambda 表达式的运用 + try { + semaphore.acquire();// 获取一个许可,所以可运行线程数量为20/1=20 + test(threadnum); + semaphore.release();// 释放一个许可 + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + }); + } + threadPool.shutdown(); + System.out.println("finish"); + } + + public static void test(int threadnum) throws InterruptedException { + Thread.sleep(1000);// 模拟请求的耗时操作 + System.out.println("threadnum:" + threadnum); + Thread.sleep(1000);// 模拟请求的耗时操作 + } +} +``` + +执行 `acquire` 方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个 `release` 方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。 Semaphore经常用于限制获取某种资源的线程数量。 + +当然一次也可以一次拿取和释放多个许可,不过一般没有必要这样做: + +```java + semaphore.acquire(5);// 获取5个许可,所以可运行线程数量为20/5=4 + test(threadnum); + semaphore.release(5);// 获取5个许可,所以可运行线程数量为20/5=4 +``` + +除了 `acquire`方法之外,另一个比较常用的与之对应的方法是`tryAcquire`方法,该方法如果获取不到许可就立即返回false。 + + +Semaphore 有两种模式,公平模式和非公平模式。 + +- **公平模式:** 调用acquire的顺序就是获取许可证的顺序,遵循FIFO; +- **非公平模式:** 抢占式的。 + +**Semaphore 对应的两个构造方法如下:** + +```java + public Semaphore(int permits) { + sync = new NonfairSync(permits); + } + + public Semaphore(int permits, boolean fair) { + sync = fair ? new FairSync(permits) : new NonfairSync(permits); + } +``` +**这两个构造方法,都必须提供许可的数量,第二个构造方法可以指定是公平模式还是非公平模式,默认非公平模式。** + +由于篇幅问题,如果对 Semaphore 源码感兴趣的朋友可以看下面这篇文章: + +- https://blog.csdn.net/qq_19431333/article/details/70212663 + +### 4 CountDownLatch (倒计时器) + +CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。在Java并发中,countdownlatch的概念是一个常见的面试题,所以一定要确保你很好的理解了它。 + +#### 4.1 CountDownLatch 的三种典型用法 + +①某一线程在开始运行前等待n个线程执行完毕。将 CountDownLatch 的计数器初始化为n :`new CountDownLatch(n) `,每当一个任务线程执行完毕,就将计数器减1 `countdownlatch.countDown()`,当计数器的值变为0时,在`CountDownLatch上 await()` 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。 + +②实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的 `CountDownLatch` 对象,将其计数器初始化为 1 :`new CountDownLatch(1) `,多个线程在开始执行任务前首先 `coundownlatch.await()`,当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。 + +③死锁检测:一个非常方便的使用场景是,你可以使用n个线程访问共享资源,在每次测试阶段的线程数目是不同的,并尝试产生死锁。 + +#### 4.2 CountDownLatch 的使用示例 + +```java +/** + * + * @author SnailClimb + * @date 2018年10月1日 + * @Description: CountDownLatch 使用方法示例 + */ +public class CountDownLatchExample1 { + // 请求的数量 + private static final int threadCount = 550; + + public static void main(String[] args) throws InterruptedException { + // 创建一个具有固定线程数量的线程池对象(如果这里线程池的线程数量给太少的话你会发现执行的很慢) + ExecutorService threadPool = Executors.newFixedThreadPool(300); + final CountDownLatch countDownLatch = new CountDownLatch(threadCount); + for (int i = 0; i < threadCount; i++) { + final int threadnum = i; + threadPool.execute(() -> {// Lambda 表达式的运用 + try { + test(threadnum); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + countDownLatch.countDown();// 表示一个请求已经被完成 + } + + }); + } + countDownLatch.await(); + threadPool.shutdown(); + System.out.println("finish"); + } + + public static void test(int threadnum) throws InterruptedException { + Thread.sleep(1000);// 模拟请求的耗时操作 + System.out.println("threadnum:" + threadnum); + Thread.sleep(1000);// 模拟请求的耗时操作 + } +} + +``` +上面的代码中,我们定义了请求的数量为550,当这550个请求被处理完成之后,才会执行`System.out.println("finish");`。 + +与CountDownLatch的第一次交互是主线程等待其他线程。主线程必须在启动其他线程后立即调用CountDownLatch.await()方法。这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务。 + +其他N个线程必须引用闭锁对象,因为他们需要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是通过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。所以当N个线程都调 用了这个方法,count的值等于0,然后主线程就能通过await()方法,恢复执行自己的任务。 + +#### 4.3 CountDownLatch 的不足 + +CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。 + +#### 4.4 CountDownLatch相常见面试题: + +解释一下CountDownLatch概念? + +CountDownLatch 和CyclicBarrier的不同之处? + +给出一些CountDownLatch使用的例子? + +CountDownLatch 类中主要的方法? + +### 5 CyclicBarrier(循环栅栏) + +CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。 + +CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 `CyclicBarrier(int parties)`,其参数表示屏障拦截的线程数量,每个线程调用`await`方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 + +#### 5.1 CyclicBarrier 的应用场景 + +CyclicBarrier 可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。 + +#### 5.2 CyclicBarrier 的使用示例 + +示例1: + +```java +/** + * + * @author Snailclimb + * @date 2018年10月1日 + * @Description: 测试 CyclicBarrier 类中带参数的 await() 方法 + */ +public class CyclicBarrierExample2 { + // 请求的数量 + private static final int threadCount = 550; + // 需要同步的线程数量 + private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5); + + public static void main(String[] args) throws InterruptedException { + // 创建线程池 + ExecutorService threadPool = Executors.newFixedThreadPool(10); + + for (int i = 0; i < threadCount; i++) { + final int threadNum = i; + Thread.sleep(1000); + threadPool.execute(() -> { + try { + test(threadNum); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BrokenBarrierException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + } + threadPool.shutdown(); + } + + public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { + System.out.println("threadnum:" + threadnum + "is ready"); + try { + /**等待60秒,保证子线程完全执行结束*/ + cyclicBarrier.await(60, TimeUnit.SECONDS); + } catch (Exception e) { + System.out.println("-----CyclicBarrierException------"); + } + System.out.println("threadnum:" + threadnum + "is finish"); + } + +} +``` + +运行结果,如下: + +``` +threadnum:0is ready +threadnum:1is ready +threadnum:2is ready +threadnum:3is ready +threadnum:4is ready +threadnum:4is finish +threadnum:0is finish +threadnum:1is finish +threadnum:2is finish +threadnum:3is finish +threadnum:5is ready +threadnum:6is ready +threadnum:7is ready +threadnum:8is ready +threadnum:9is ready +threadnum:9is finish +threadnum:5is finish +threadnum:8is finish +threadnum:7is finish +threadnum:6is finish +...... +``` +可以看到当线程数量也就是请求数量达到我们定义的 5 个的时候, `await`方法之后的方法才被执行。 + +另外,CyclicBarrier还提供一个更高级的构造函数`CyclicBarrier(int parties, Runnable barrierAction)`,用于在线程到达屏障时,优先执行`barrierAction`,方便处理更复杂的业务场景。示例代码如下: + +```java +/** + * + * @author SnailClimb + * @date 2018年10月1日 + * @Description: 新建 CyclicBarrier 的时候指定一个 Runnable + */ +public class CyclicBarrierExample3 { + // 请求的数量 + private static final int threadCount = 550; + // 需要同步的线程数量 + private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> { + System.out.println("------当线程数达到之后,优先执行------"); + }); + + public static void main(String[] args) throws InterruptedException { + // 创建线程池 + ExecutorService threadPool = Executors.newFixedThreadPool(10); + + for (int i = 0; i < threadCount; i++) { + final int threadNum = i; + Thread.sleep(1000); + threadPool.execute(() -> { + try { + test(threadNum); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (BrokenBarrierException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + }); + } + threadPool.shutdown(); + } + + public static void test(int threadnum) throws InterruptedException, BrokenBarrierException { + System.out.println("threadnum:" + threadnum + "is ready"); + cyclicBarrier.await(); + System.out.println("threadnum:" + threadnum + "is finish"); + } + +} +``` + +运行结果,如下: + +``` +threadnum:0is ready +threadnum:1is ready +threadnum:2is ready +threadnum:3is ready +threadnum:4is ready +------当线程数达到之后,优先执行------ +threadnum:4is finish +threadnum:0is finish +threadnum:2is finish +threadnum:1is finish +threadnum:3is finish +threadnum:5is ready +threadnum:6is ready +threadnum:7is ready +threadnum:8is ready +threadnum:9is ready +------当线程数达到之后,优先执行------ +threadnum:9is finish +threadnum:5is finish +threadnum:6is finish +threadnum:8is finish +threadnum:7is finish +...... +``` +#### 5.3 CyclicBarrier和CountDownLatch的区别 + +CountDownLatch是计数器,只能使用一次,而CyclicBarrier的计数器提供reset功能,可以多次使用。但是我不那么认为它们之间的区别仅仅就是这么简单的一点。我们来从jdk作者设计的目的来看,javadoc是这么描述它们的: + +> CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(CountDownLatch: 一个或者多个线程,等待其他多个线程完成某件事情之后才能执行;) +> CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.(CyclicBarrier : 多个线程互相等待,直到到达同一个同步点,再继续一起执行。) + +对于CountDownLatch来说,重点是“一个线程(多个线程)等待”,而其他的N个线程在完成“某件事情”之后,可以终止,也可以等待。而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待。 + +CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减,而CyclicBarrier更像是一个阀门,需要所有线程都到达,阀门才能打开,然后继续执行。 + +![CyclicBarrier和CountDownLatch的区别](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS333.png) + +CyclicBarrier和CountDownLatch的区别这部分内容参考了如下两篇文章: + +- https://blog.csdn.net/u010185262/article/details/54692886 +- https://blog.csdn.net/tolcf/article/details/50925145?utm_source=blogxgwz0 + +### 6 ReentrantLock 和 ReentrantReadWriteLock + +ReentrantLock 和 synchronized 的区别在上面已经讲过了这里就不多做讲解。另外,需要注意的是:读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了。 + +由于篇幅问题,关于 ReentrantLock 和 ReentrantReadWriteLock 详细内容可以查看我的这篇原创文章。 + +- [ReentrantLock 和 ReentrantReadWriteLock](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483745&idx=2&sn=6778ee954a19816310df54ef9a3c2f8a&chksm=fd985700caefde16b9970f5e093b0c140d3121fb3a8458b11871e5e9723c5fd1b5a961fd2228&token=1829606453&lang=zh_CN#rd) diff --git a/Java/Multithread/Atomic.md b/Java/Multithread/Atomic.md new file mode 100644 index 00000000000..33dd7ef3d9b --- /dev/null +++ b/Java/Multithread/Atomic.md @@ -0,0 +1,337 @@ +> 个人觉得这一节掌握基本的使用即可! + +**本节思维导图:** + +![](https://user-gold-cdn.xitu.io/2018/10/30/166c58b785368234?w=1200&h=657&f=png&s=49615) + +### 1 Atomic 原子类介绍 + +Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 + +所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 + +并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 + +![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) + +根据操作的数据类型,可以将JUC包中的原子类分为4类 + +**基本类型** + +使用原子的方式更新基本类型 + +- AtomicInteger:整型原子类 +- AtomicLong:长整型原子类 +- AtomicBoolean :布尔型原子类 + +**数组类型** + +使用原子的方式更新数组里的某个元素 + + +- AtomicIntegerArray:整型数组原子类 +- AtomicLongArray:长整型数组原子类 +- AtomicReferenceArray :引用类型数组原子类 + +**引用类型** + +- AtomicReference:引用类型原子类 +- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicMarkableReference :原子更新带有标记位的引用类型 + +**对象的属性修改类型** + +- AtomicIntegerFieldUpdater:原子更新整型字段的更新器 +- AtomicLongFieldUpdater:原子更新长整型字段的更新器 +- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + +下面我们来详细介绍一下这些原子类。 + +### 2 基本类型原子类 + +#### 2.1 基本类型原子类介绍 + +使用原子的方式更新基本类型 + +- AtomicInteger:整型原子类 +- AtomicLong:长整型原子类 +- AtomicBoolean :布尔型原子类 + +上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。 + + **AtomicInteger 类常用方法** + +```java +public final int get() //获取当前的值 +public final int getAndSet(int newValue)//获取当前的值,并设置新的值 +public final int getAndIncrement()//获取当前的值,并自增 +public final int getAndDecrement() //获取当前的值,并自减 +public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 +boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) +public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 +``` + +#### 2.2 AtomicInteger 常见方法使用 + +```java +import java.util.concurrent.atomic.AtomicInteger; + +public class AtomicIntegerTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int temvalue = 0; + AtomicInteger i = new AtomicInteger(0); + temvalue = i.getAndSet(3); + System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3 + temvalue = i.getAndIncrement(); + System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4 + temvalue = i.getAndAdd(5); + System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9 + } + +} +``` + +#### 2.3 基本数据类型原子类的优势 + +通过一个简单例子带大家看一下基本数据类型原子类的优势 + +**①多线程环境不使用原子类保证线程安全(基本数据类型)** + +```java +class Test { + private volatile int count = 0; + //若要线程安全执行执行count++,需要加锁 + public synchronized void increment() { + count++; + } + + public int getCount() { + return count; + } +} +``` +**②多线程环境使用原子类保证线程安全(基本数据类型)** + +```java +class Test2 { + private AtomicInteger count = new AtomicInteger(); + + public void increment() { + count.incrementAndGet(); + } + //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。 + public int getCount() { + return count.get(); + } +} + +``` +#### 2.4 AtomicInteger 线程安全原理简单分析 + +AtomicInteger 类的部分源码: + +```java + // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; +``` + +AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 + +CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 + + +### 3 数组类型原子类 + +#### 3.1 数组类型原子类介绍 + +使用原子的方式更新数组里的某个元素 + + +- AtomicIntegerArray:整形数组原子类 +- AtomicLongArray:长整形数组原子类 +- AtomicReferenceArray :引用类型数组原子类 + +上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。 + +**AtomicIntegerArray 类常用方法** + +```java +public final int get(int i) //获取 index=i 位置元素的值 +public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue +public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增 +public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减 +public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值 +boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update) +public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 +``` +#### 3.2 AtomicIntegerArray 常见方法使用 + +```java + +import java.util.concurrent.atomic.AtomicIntegerArray; + +public class AtomicIntegerArrayTest { + + public static void main(String[] args) { + // TODO Auto-generated method stub + int temvalue = 0; + int[] nums = { 1, 2, 3, 4, 5, 6 }; + AtomicIntegerArray i = new AtomicIntegerArray(nums); + for (int j = 0; j < nums.length; j++) { + System.out.println(i.get(j)); + } + temvalue = i.getAndSet(0, 2); + System.out.println("temvalue:" + temvalue + "; i:" + i); + temvalue = i.getAndIncrement(0); + System.out.println("temvalue:" + temvalue + "; i:" + i); + temvalue = i.getAndAdd(0, 5); + System.out.println("temvalue:" + temvalue + "; i:" + i); + } + +} +``` + +### 4 引用类型原子类 + +#### 4.1 引用类型原子类介绍 + +基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。 + +- AtomicReference:引用类型原子类 +- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicMarkableReference :原子更新带有标记位的引用类型 + +上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。 + +#### 4.2 AtomicReference 类使用示例 + +```java +import java.util.concurrent.atomic.AtomicReference; + +public class AtomicReferenceTest { + + public static void main(String[] args) { + AtomicReference ar = new AtomicReference(); + Person person = new Person("SnailClimb", 22); + ar.set(person); + Person updatePerson = new Person("Daisy", 20); + ar.compareAndSet(person, updatePerson); + + System.out.println(ar.get().getName()); + System.out.println(ar.get().getAge()); + } +} + +class Person { + private String name; + private int age; + + public Person(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + +} +``` +上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下: + +``` +Daisy +20 +``` + + +### 5 对象的属性修改类型原子类 + +#### 5.1 对象的属性修改类型原子类介绍 + +如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。 + +- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 +- AtomicLongFieldUpdater:原子更新长整形字段的更新器 +- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + +要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。 + +上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。 + +#### 5.2 AtomicIntegerFieldUpdater 类使用示例 + +```java +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; + +public class AtomicIntegerFieldUpdaterTest { + public static void main(String[] args) { + AtomicIntegerFieldUpdater a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); + + User user = new User("Java", 22); + System.out.println(a.getAndIncrement(user));// 22 + System.out.println(a.get(user));// 23 + } +} + +class User { + private String name; + public volatile int age; + + public User(String name, int age) { + super(); + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + +} +``` + +输出结果: + +``` +22 +23 +``` + diff --git "a/Java/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" "b/Java/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" new file mode 100644 index 00000000000..10d6177b040 --- /dev/null +++ "b/Java/Multithread/BATJ\351\203\275\347\210\261\351\227\256\347\232\204\345\244\232\347\272\277\347\250\213\351\235\242\350\257\225\351\242\230.md" @@ -0,0 +1,429 @@ + + + +# 一 面试中关于 synchronized 关键字的 5 连击 + +### 1.1 说一说自己对于 synchronized 关键字的了解 + +synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。 + +另外,在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 + + +### 1.2 说说自己是怎么使用 synchronized 关键字,在项目中用到了吗 + +**synchronized关键字最主要的三种使用方式:** + +- **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** +- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 +- **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! + +下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 + +面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” + + + +**双重校验锁实现对象单例(线程安全)** + +```java +public class Singleton { + + private volatile static Singleton uniqueInstance; + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + //先判断对象是否已经实例过,没有实例化过才进入加锁代码 + if (uniqueInstance == null) { + //类对象加锁 + synchronized (Singleton.class) { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } +} +``` +另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 + +uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: + +1. 为 uniqueInstance 分配内存空间 +2. 初始化 uniqueInstance +3. 将 uniqueInstance 指向分配的内存地址 + +但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出先问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 + +使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 + +### 1.3 讲一下 synchronized 关键字的底层原理 + +**synchronized 关键字底层原理属于 JVM 层面。** + +**① synchronized 同步语句块的情况** + +```java +public class SynchronizedDemo { + public void method() { + synchronized (this) { + System.out.println("synchronized 代码块"); + } + } +} + +``` + +通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 + +![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add616a292bcf?w=917&h=633&f=png&s=21863) + +从上面我们可以看出: + +**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 + +**② synchronized 修饰方法的的情况** + +```java +public class SynchronizedDemo2 { + public synchronized void method() { + System.out.println("synchronized 方法"); + } +} + +``` + +![synchronized 关键字原理](https://user-gold-cdn.xitu.io/2018/10/26/166add6169fc206d?w=875&h=421&f=png&s=16114) + +synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 + + +### 1.4 说说 JDK1.6 之后的synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗 + +JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 + +锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 + +关于这几种优化的详细信息可以查看:[synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1604028915&lang=zh_CN#rd) + +### 1.5 谈谈 synchronized和ReenTrantLock 的区别 + + +**① 两者都是可重入锁** + +两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 + +**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** + +synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 + +**③ ReenTrantLock 比 synchronized 增加了一些高级功能** + +相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** + +- **ReenTrantLock提供了一种能够中断等待锁的线程的机制**,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。 +- **ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReenTrantLock默认情况是非公平的,可以通过 ReenTrantLock类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。 +- synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器),**线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”** ,这个功能非常重要,而且是Condition接口默认提供的。而synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。 + +如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 + +**④ 性能已不是选择标准** + +# 二 面试中关于线程池的 4 连击 + +### 2.1 讲一下Java内存模型 + + +在 JDK1.2 之前,Java的内存模型实现总是从**主存**(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存**本地内存**(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成**数据的不一致**。 + +![数据的不一致](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4423ba2?w=273&h=166&f=jpeg&s=7268) + +要解决这个问题,就需要把变量声明为 **volatile**,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。 + +说白了, **volatile** 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。 + +![volatile关键字的可见性](https://user-gold-cdn.xitu.io/2018/10/30/166c46ede4b9f501?w=474&h=238&f=jpeg&s=9942) + + +### 2.2 说说 synchronized 关键字和 volatile 关键字的区别 + + synchronized关键字和volatile关键字比较 + +- **volatile关键字**是线程同步的**轻量级实现**,所以**volatile性能肯定比synchronized关键字要好**。但是**volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块**。synchronized关键字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁以及其它各种优化之后执行效率有了显著提升,**实际开发中使用 synchronized 关键字的场景还是更多一些**。 +- **多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞** +- **volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。** +- **volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。** + + +# 三 面试中关于 线程池的 2 连击 + + +### 3.1 为什么要用线程池? + +线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。 + +这里借用《Java并发编程的艺术》提到的来说一下使用线程池的好处: + +- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 +- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。 +- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 + + +### 3.2 实现Runnable接口和Callable接口的区别 + +如果想让线程池执行任务的话需要实现的Runnable接口或Callable接口。 Runnable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。两者的区别在于 Runnable 接口不会返回结果但是 Callable 接口可以返回结果。 + + **备注:** 工具类`Executors`可以实现`Runnable`对象和`Callable`对象之间的相互转换。(`Executors.callable(Runnable task)`或`Executors.callable(Runnable task,Object resule)`)。 + +### 3.3 执行execute()方法和submit()方法的区别是什么呢? + + 1)**`execute()` 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;** + + 2)**submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功**,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 `get(long timeout,TimeUnit unit)`方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。 + + +### 3.4 如何创建线程池 + +《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险 + +> Executors 返回线程池对象的弊端如下: +> +> - **FixedThreadPool 和 SingleThreadExecutor** : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。 +> - **CachedThreadPool 和 ScheduledThreadPool** : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。 + +**方式一:通过构造方法实现** +![通过构造方法实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baac923e9?w=925&h=158&f=jpeg&s=29190) +**方式二:通过Executor 框架的工具类Executors来实现** +我们可以创建三种类型的ThreadPoolExecutor: + +- **FixedThreadPool** : 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。 +- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。 +- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。 + +对应Executors工具类中的方法如图所示: +![通过Executor 框架的工具类Executors来实现](https://user-gold-cdn.xitu.io/2018/10/30/166c4a5baa9ca5e9?w=645&h=222&f=jpeg&s=31710) + + +# 四 面试中关于 Atomic 原子类的 4 连击 + +### 4.1 介绍一下Atomic 原子类 + +Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。 + +所以,所谓原子类说简单点就是具有原子/原子操作特征的类。 + + +并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 + +![ JUC 原子类概览](https://user-gold-cdn.xitu.io/2018/10/30/166c4ac08d4c5547?w=317&h=367&f=png&s=13267) + +### 4.2 JUC 包中的原子类是哪4类? + +**基本类型** + +使用原子的方式更新基本类型 + +- AtomicInteger:整形原子类 +- AtomicLong:长整型原子类 +- AtomicBoolean :布尔型原子类 + +**数组类型** + +使用原子的方式更新数组里的某个元素 + + +- AtomicIntegerArray:整形数组原子类 +- AtomicLongArray:长整形数组原子类 +- AtomicReferenceArray :引用类型数组原子类 + +**引用类型** + +- AtomicReference:引用类型原子类 +- AtomicStampedRerence:原子更新引用类型里的字段原子类 +- AtomicMarkableReference :原子更新带有标记位的引用类型 + +**对象的属性修改类型** + +- AtomicIntegerFieldUpdater:原子更新整形字段的更新器 +- AtomicLongFieldUpdater:原子更新长整形字段的更新器 +- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。 + + +### 4.3 讲讲 AtomicInteger 的使用 + + **AtomicInteger 类常用方法** + +```java +public final int get() //获取当前的值 +public final int getAndSet(int newValue)//获取当前的值,并设置新的值 +public final int getAndIncrement()//获取当前的值,并自增 +public final int getAndDecrement() //获取当前的值,并自减 +public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 +boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) +public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。 +``` + + **AtomicInteger 类的使用示例** + +使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。 +```java +class AtomicIntegerTest { + private AtomicInteger count = new AtomicInteger(); + //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 + public void increment() { + count.incrementAndGet(); + } + + public int getCount() { + return count.get(); + } +} + +``` + +### 4.4 能不能给我简单介绍一下 AtomicInteger 类的原理 + +AtomicInteger 线程安全原理简单分析 + +AtomicInteger 类的部分源码: + +```java + // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) + private static final Unsafe unsafe = Unsafe.getUnsafe(); + private static final long valueOffset; + + static { + try { + valueOffset = unsafe.objectFieldOffset + (AtomicInteger.class.getDeclaredField("value")); + } catch (Exception ex) { throw new Error(ex); } + } + + private volatile int value; +``` + +AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 + +CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。 + +关于 Atomic 原子类这部分更多内容可以查看我的这篇文章:并发编程面试必备:[JUC 中的 Atomic 原子类总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) + +# 五 AQS + +### 5.1 AQS 介绍 + +AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。 + +![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bb575d4a690?w=317&h=338&f=png&s=14122) + +AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。当然,我们自己也能利用AQS非常轻松容易地构造出符合我们自己需求的同步器。 + +### 5.2 AQS 原理分析 + +AQS 原理这部分参考了部分博客,在5.2节末尾放了链接。 + +> 在面试中被问到并发知识的时候,大多都会被问到“请你说一下自己对于AQS原理的理解”。下面给大家一个示例供大家参加,面试不是背题,大家一定要假如自己的思想,即使加入不了自己的思想也要保证自己能够通俗的讲出来而不是背出来。 + +下面大部分内容其实在AQS类注释上已经给出了,不过是英语看着比较吃力一点,感兴趣的话可以看看源码。 + +#### 5.2.1 AQS 原理概览 + + + +**AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。** + +> CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。 + +看个AQS(AbstractQueuedSynchronizer)原理图: + + +![enter image description here](https://user-gold-cdn.xitu.io/2018/10/30/166c4bbe4a9c5ae7?w=852&h=401&f=png&s=21797) + +AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。 + +```java +private volatile int state;//共享变量,使用volatile修饰保证线程可见性 +``` + +状态信息通过procted类型的getState,setState,compareAndSetState进行操作 + +```java + +//返回同步状态的当前值 +protected final int getState() { + return state; +} + // 设置同步状态的值 +protected final void setState(int newState) { + state = newState; +} +//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值) +protected final boolean compareAndSetState(int expect, int update) { + return unsafe.compareAndSwapInt(this, stateOffset, expect, update); +} +``` + +#### 5.2.2 AQS 对资源的共享方式 + +**AQS定义两种资源共享方式** + +- **Exclusive**(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁: + - 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁 + - 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的 +- **Share**(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。 + +ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。 + +不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。 + +#### 5.2.3 AQS底层使用了模板方法模式 + +同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用): + +1. 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) +2. 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。 + +这和我们以往通过实现接口的方式有很大区别,这是模板方法模式很经典的一个运用。 + +**AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法:** + +```java +isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。 +tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。 +tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。 +tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。 +tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。 + +``` + +默认情况下,每个方法都抛出 `UnsupportedOperationException`。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用。 + +以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。 + +再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。 + +一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现`tryAcquire-tryRelease`、`tryAcquireShared-tryReleaseShared`中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如`ReentrantReadWriteLock`。 + +推荐两篇 AQS 原理和相关源码分析的文章: + +- http://www.cnblogs.com/waterystone/p/4920797.html +- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html + +### 5.3 AQS 组件总结 + +- **Semaphore(信号量)-允许多个线程同时访问:** synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。 +- **CountDownLatch (倒计时器):** CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计时结束,再开始执行。 +- **CyclicBarrier(循环栅栏):** CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。 + +关于AQS这部分的更多内容可以查看我的这篇文章:[并发编程面试必备:AQS 原理以及 AQS 同步组件总结](https://mp.weixin.qq.com/s/joa-yOiTrYF67bElj8xqvg) + +# Reference + +- 《深入理解 Java 虚拟机》 +- 《实战 Java 高并发程序设计》 +- 《Java并发编程的艺术》 +- http://www.cnblogs.com/waterystone/p/4920797.html +- https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html diff --git "a/Java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" "b/Java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..96a9fe23176 --- /dev/null +++ "b/Java/Multithread/\345\271\266\345\217\221\345\256\271\345\231\250\346\200\273\347\273\223.md" @@ -0,0 +1,223 @@ + + + +- [一 JDK 提供的并发容器总结](#一-jdk-提供的并发容器总结) +- [二 ConcurrentHashMap](#二-concurrenthashmap) +- [三 CopyOnWriteArrayList](#三-copyonwritearraylist) + - [3.1 CopyOnWriteArrayList 简介](#31-copyonwritearraylist-简介) + - [3.2 CopyOnWriteArrayList 是如何做到的?](#32-copyonwritearraylist-是如何做到的?) + - [3.3 CopyOnWriteArrayList 读取和写入源码简单分析](#33-copyonwritearraylist-读取和写入源码简单分析) + - [3.3.1 CopyOnWriteArrayList 读取操作的实现](#331-copyonwritearraylist-读取操作的实现) + - [3.3.2 CopyOnWriteArrayList 写入操作的实现](#332-copyonwritearraylist-写入操作的实现) +- [四 ConcurrentLinkedQueue](#四-concurrentlinkedqueue) +- [五 BlockingQueue](#五-blockingqueue) + - [5.1 BlockingQueue 简单介绍](#51-blockingqueue-简单介绍) + - [5.2 ArrayBlockingQueue](#52-arrayblockingqueue) + - [5.3 LinkedBlockingQueue](#53-linkedblockingqueue) + - [5.4 PriorityBlockingQueue](#54-priorityblockingqueue) +- [六 ConcurrentSkipListMap](#六-concurrentskiplistmap) +- [七 参考](#七-参考) + + + +## 一 JDK 提供的并发容器总结 + +JDK提供的这些容器大部分在 `java.util.concurrent` 包中。 + + +- **ConcurrentHashMap:** 线程安全的HashMap +- **CopyOnWriteArrayList:** 线程安全的List,在读多写少的场合性能非常好,远远好于Vector. +- **ConcurrentLinkedQueue:** 高效的并发队列,使用链表实现。可以看做一个线程安全的 LinkedList,这是一个非阻塞队列。 +- **BlockingQueue:** 这是一个接口,JDK内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。 +- **ConcurrentSkipListMap:** 跳表的实现。这是一个Map,使用跳表的数据结构进行快速查找。 + +## 二 ConcurrentHashMap + +我们知道 HashMap 不是线程安全的,在并发场景下如果要保证一种可行的方式是使用 `Collections.synchronizedMap()` 方法来包装我们的 HashMap。但这是通过使用一个全局的锁来同步不同线程间的并发访问,因此会带来不可忽视的性能问题。 + +所以就有了 HashMap 的线程安全版本—— ConcurrentHashMap 的诞生。在ConcurrentHashMap中,无论是读操作还是写操作都能保证很高的性能:在进行读操作时(几乎)不需要加锁,而在写操作时通过锁分段技术只对所操作的段加锁而不影响客户端对其它段的访问。 + +关于 ConcurrentHashMap 相关问题,我在 [《这几道Java集合框架面试题几乎必问》](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md) 这篇文章中已经提到过。下面梳理一下关于 ConcurrentHashMap 比较重要的问题: + +- [ConcurrentHashMap 和 Hashtable 的区别](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap-%E5%92%8C-hashtable-%E7%9A%84%E5%8C%BA%E5%88%AB) +- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](https://github.com/Snailclimb/JavaGuide/blob/master/Java%E7%9B%B8%E5%85%B3/%E8%BF%99%E5%87%A0%E9%81%93Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E9%9D%A2%E8%AF%95%E9%A2%98%E5%87%A0%E4%B9%8E%E5%BF%85%E9%97%AE.md#concurrenthashmap%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%E5%BA%95%E5%B1%82%E5%85%B7%E4%BD%93%E5%AE%9E%E7%8E%B0) + + + +## 三 CopyOnWriteArrayList + +### 3.1 CopyOnWriteArrayList 简介 + +```java +public class CopyOnWriteArrayList +extends Object +implements List, RandomAccess, Cloneable, Serializable +``` + +在很多应用场景中,读操作可能会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是安全的。 + +这和我们之前在多线程章节讲过 `ReentrantReadWriteLock` 读写锁的思想非常类似,也就是读读共享、写写互斥、读写互斥、写读互斥。JDK中提供了 `CopyOnWriteArrayList` 类比相比于在读写锁的思想又更进一步。为了将读取的性能发挥到极致,`CopyOnWriteArrayList` 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。**那它是怎么做的呢?** + +### 3.2 CopyOnWriteArrayList 是如何做到的? + + `CopyOnWriteArrayList` 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。 + +从 `CopyOnWriteArrayList` 的名字就能看出`CopyOnWriteArrayList` 是满足`CopyOnWrite` 的ArrayList,所谓`CopyOnWrite` 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。 + +### 3.3 CopyOnWriteArrayList 读取和写入源码简单分析 + +#### 3.3.1 CopyOnWriteArrayList 读取操作的实现 + +读取操作没有任何同步控制和锁操作,理由就是内部数组 array 不会发生修改,只会被另外一个 array 替换,因此可以保证数据安全。 + +```java + /** The array, accessed only via getArray/setArray. */ + private transient volatile Object[] array; + public E get(int index) { + return get(getArray(), index); + } + @SuppressWarnings("unchecked") + private E get(Object[] a, int index) { + return (E) a[index]; + } + final Object[] getArray() { + return array; + } + +``` + +#### 3.3.2 CopyOnWriteArrayList 写入操作的实现 + +CopyOnWriteArrayList 写入操作 add() 方法在添加集合的时候加了锁,保证了同步,避免了多线程写的时候会 copy 出多个副本出来。 + +```java + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(E e) { + final ReentrantLock lock = this.lock; + lock.lock();//加锁 + try { + Object[] elements = getArray(); + int len = elements.length; + Object[] newElements = Arrays.copyOf(elements, len + 1);//拷贝新数组 + newElements[len] = e; + setArray(newElements); + return true; + } finally { + lock.unlock();//释放锁 + } + } +``` + +## 四 ConcurrentLinkedQueue + +Java提供的线程安全的 Queue 可以分为**阻塞队列**和**非阻塞队列**,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。 **阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。** + +从名字可以看出,`ConcurrentLinkedQueue`这个队列使用链表作为其数据结构.ConcurrentLinkedQueue 应该算是在高并发环境中性能最好的队列了。它之所有能有很好的性能,是因为其内部复杂的实现。 + +ConcurrentLinkedQueue 内部代码我们就不分析了,大家知道ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全就好了。 + +ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。 + +## 五 BlockingQueue + +### 5.1 BlockingQueue 简单介绍 + +上面我们己经提到了 ConcurrentLinkedQueue 作为高性能的非阻塞队列。下面我们要讲到的是阻塞队列——BlockingQueue。阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。 + +BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。下面是 BlockingQueue 的相关实现类: + +![BlockingQueue 的实现类](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/51622268.jpg) + +**下面主要介绍一下:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,这三个 BlockingQueue 的实现类。** + +### 5.2 ArrayBlockingQueue + +**ArrayBlockingQueue** 是 BlockingQueue 接口的有界队列实现类,底层采用**数组**来实现。ArrayBlockingQueue一旦创建,容量不能改变。其并发控制采用可重入锁来控制,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。 + +ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在,当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue。如果保证公平性,通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码: + +```java +private static ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(10,true); +``` + +### 5.3 LinkedBlockingQueue + +**LinkedBlockingQueue** 底层基于**单向链表**实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足FIFO的特性,与ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于Integer.MAX_VALUE。 + +**相关构造方法:** + +```java + /** + *某种意义上的无界队列 + * Creates a {@code LinkedBlockingQueue} with a capacity of + * {@link Integer#MAX_VALUE}. + */ + public LinkedBlockingQueue() { + this(Integer.MAX_VALUE); + } + + /** + *有界队列 + * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity. + * + * @param capacity the capacity of this queue + * @throws IllegalArgumentException if {@code capacity} is not greater + * than zero + */ + public LinkedBlockingQueue(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException(); + this.capacity = capacity; + last = head = new Node(null); + } +``` + +### 5.4 PriorityBlockingQueue + +**PriorityBlockingQueue** 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 `compareTo()` 方法来指定元素排序规则,或者初始化时通过构造器参数 `Comparator` 来指定排序规则。 + +PriorityBlockingQueue 并发控制采用的是 **ReentrantLock**,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,**如果空间不够的话会自动扩容**)。 + +简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的(comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。 + +**推荐文章:** + +《解读 Java 并发队列 BlockingQueue》 + +[https://javadoop.com/post/java-concurrent-queue](https://javadoop.com/post/java-concurrent-queue) + +## 六 ConcurrentSkipListMap + +下面这部分内容参考了极客时间专栏[《数据结构与算法之美》](https://time.geekbang.org/column/intro/126?code=zl3GYeAsRI4rEJIBNu5B/km7LSZsPDlGWQEpAYw5Vu0=&utm_term=SPoster)以及《实战Java高并发程序设计》。 + +**为了引出ConcurrentSkipListMap,先带着大家简单理解一下跳表。** + +对于一个单链表,即使链表是有序的,如果我们想要在其中查找某个数据,也只能从头到尾遍历链表,这样效率自然就会很低,跳表就不一样了。跳表是一种可以用来快速查找的数据结构,有点类似于平衡树。它们都可以对元素进行快速的查找。但一个重要的区别是:对平衡树的插入和删除往往很可能导致平衡树进行一次全局的调整。而对跳表的插入和删除只需要对整个数据结构的局部进行操作即可。这样带来的好处是:在高并发的情况下,你会需要一个全局锁来保证整个平衡树的线程安全。而对于跳表,你只需要部分锁即可。这样,在高并发环境下,你就可以拥有更好的性能。而就查询的性能而言,跳表的时间复杂度也是 **O(logn)** 所以在并发数据结构中,JDK 使用跳表来实现一个 Map。 + +跳表的本质是同时维护了多个链表,并且链表是分层的, + +![2级索引跳表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/93666217.jpg) + +最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。 + +跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素18。 + +![在跳表中查找元素18](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/32005738.jpg) + +查找18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。 + +从上面很容易看出,**跳表是一种利用空间换时间的算法。** + +使用跳表实现Map 和使用哈希算法实现Map的另外一个不同之处是:哈希并不会保存元素的顺序,而跳表内所有的元素都是排序的。因此在对跳表进行遍历时,你会得到一个有序的结果。所以,如果你的应用需要有序性,那么跳表就是你不二的选择。JDK 中实现这一数据结构的类是ConcurrentSkipListMap。 + + + +## 七 参考 + +- 《实战Java高并发程序设计》 +- https://javadoop.com/post/java-concurrent-queue +- https://juejin.im/post/5aeebd02518825672f19c546 diff --git "a/Java/What's New in JDK8/JDK8\346\216\245\345\217\243\350\247\204\350\214\203-\351\235\231\346\200\201\343\200\201\351\273\230\350\256\244\346\226\271\346\263\225.md" "b/Java/What's New in JDK8/JDK8\346\216\245\345\217\243\350\247\204\350\214\203-\351\235\231\346\200\201\343\200\201\351\273\230\350\256\244\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..ee1dd8c2448 --- /dev/null +++ "b/Java/What's New in JDK8/JDK8\346\216\245\345\217\243\350\247\204\350\214\203-\351\235\231\346\200\201\343\200\201\351\273\230\350\256\244\346\226\271\346\263\225.md" @@ -0,0 +1,163 @@ +JDK8接口规范 +=== +在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(比如泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。。 +--- +## 1.JDK8以前的接口规范 +- JDK8以前接口可以定义的变量和方法 + - 所有变量(Field)不论是否显式 的声明为```public static final```,它实际上都是```public static final```的。 + - 所有方法(Method)不论是否显示 的声明为```public abstract```,它实际上都是```public abstract```的。 +```java +public interface AInterfaceBeforeJDK8 { + int FIELD = 0; + void simpleMethod(); +} +``` +以上接口信息反编译以后可以看到字节码信息里Filed是public static final的,而方法是public abstract的,即是你没有显示的去声明它。 +```java +{ + public static final int FIELD; + descriptor: I + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + public abstract void simpleMethod(); + descriptor: ()V + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT +} +``` +## 2.JDK8之后的接口规范 +- JDK8之后接口可以定义的变量和方法 + - 变量(Field)仍然必须是 ```java public static final```的 + - 方法(Method)除了可以是public abstract之外,还可以是public static或者是default(相当于仅public修饰的实例方法)的。 +从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。 +
下面是一个JDK8之后的接口例子 +```java +public interface AInterfaceInJDK8 { + int simpleFiled = 0; + static int staticField = 1; + + public static void main(String[] args) { + } + static void staticMethod(){} + + default void defaultMethod(){} + + void simpleMethod() throws IOException; + +} +``` +进行反编译(去除了一些没用信息) +```java +{ + public static final int simpleFiled; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + + public static final int staticField; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + + public static void main(java.lang.String[]); + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public static void staticMethod(); + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public void defaultMethod(); + flags: (0x0001) ACC_PUBLIC + + public abstract void simpleMethod() throws java.io.IOException; + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT + Exceptions: + throws java.io.IOException +} +``` +可以看到 default关键字修饰的方法是像实例方法一样定义的,所以我们来定义一个只有default的方法并且实现一下试一试。 +```java +interface Default { + default int defaultMethod() { + return 4396; + } +} + +public class DefaultMethod implements Default { + public static void main(String[] args) { + DefaultMethod defaultMethod = new DefaultMethod(); + System.out.println(defaultMethod.defaultMethod()); + //compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context + //! DefaultMethod.defaultMethod(); + } +} +``` +可以看到default方法确实像实例方法一样,必须有实例对象才能调用,并且子类在实现接口时,可以不用实现default方法,也可以覆盖该方法。 +这有点像子类继承父类实例方法。 +
+接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用** +```java +interface Static { + static int staticMethod() { + return 4396; + } +} + ... main(String...args) + //!compile error: Static method may be invoked on containing interface class only + //!aInstanceOfStatic.staticMethod(); + ... +``` +另一个问题是多继承问题,大家知道Java中类是不支持多继承的,但是接口是多继承和多实现(implements后跟多个接口)的, +那么如果一个接口继承另一个接口,两个接口都有同名的default方法会怎么样呢?答案是会像类继承一样覆写(@Override),以下代码在IDE中可以顺利编译 +```java +interface Default { + default int defaultMethod() { + return 4396; + } +} +interface Default2 extends Default { + @Override + default int defaultMethod() { + return 9527; + } +} +public class DefaultMethod implements Default,Default2 { + public static void main(String[] args) { + DefaultMethod defaultMethod = new DefaultMethod(); + System.out.println(defaultMethod.defaultMethod()); + } +} + +输出 : 9527 +``` +出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。 +
+那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?IDE会要求你重写这个冲突的方法,让你自己选择去执行哪个方法,因为IDE它 +还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。 +```java +interface Default { + default int defaultMethod() { + return 4396; + } +} + +interface Default2 { + default int defaultMethod() { + return 9527; + } +} +//如果不重写 +//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2 +public class DefaultMethod implements Default,Default2 { +@Override + public int defaultMethod() { + System.out.println(Default.super.defaultMethod()); + System.out.println(Default2.super.defaultMethod()); + return 996; + } + public static void main(String[] args) { + DefaultMethod defaultMethod = new DefaultMethod(); + System.out.println(defaultMethod.defaultMethod()); + } +} + +运行输出 : +4396 +9527 +996 +``` diff --git a/Java/What's New in JDK8/Java8Tutorial.md b/Java/What's New in JDK8/Java8Tutorial.md new file mode 100644 index 00000000000..4dd12ecf063 --- /dev/null +++ b/Java/What's New in JDK8/Java8Tutorial.md @@ -0,0 +1,924 @@ +随着 Java 8 的普及度越来越高,很多人都提到面试中关于Java 8 也是非常常问的知识点。应各位要求和需要,我打算对这部分知识做一个总结。本来准备自己总结的,后面看到Github 上有一个相关的仓库,地址: +[https://github.com/winterbe/java8-tutorial](https://github.com/winterbe/java8-tutorial)。这个仓库是英文的,我对其进行了翻译并添加和修改了部分内容,下面是正文了。 + + + +- [Java 8 Tutorial](#java-8-tutorial) + - [接口的默认方法\(Default Methods for Interfaces\)](#接口的默认方法default-methods-for-interfaces) + - [Lambda表达式\(Lambda expressions\)](#lambda表达式lambda-expressions) + - [函数式接口\(Functional Interfaces\)](#函数式接口functional-interfaces) + - [方法和构造函数引用\(Method and Constructor References\)](#方法和构造函数引用method-and-constructor-references) + - [Lamda 表达式作用域\(Lambda Scopes\)](#lamda-表达式作用域lambda-scopes) + - [访问局部变量](#访问局部变量) + - [访问字段和静态变量](#访问字段和静态变量) + - [访问默认接口方法](#访问默认接口方法) + - [内置函数式接口\(Built-in Functional Interfaces\)](#内置函数式接口built-in-functional-interfaces) + - [Predicates](#predicates) + - [Functions](#functions) + - [Suppliers](#suppliers) + - [Consumers](#consumers) + - [Comparators](#comparators) + - [Optionals](#optionals) + - [Streams\(流\)](#streams流) + - [Filter\(过滤\)](#filter过滤) + - [Sorted\(排序\)](#sorted排序) + - [Map\(映射\)](#map映射) + - [Match\(匹配\)](#match匹配) + - [Count\(计数\)](#count计数) + - [Reduce\(规约\)](#reduce规约) + - [Parallel Streams\(并行流\)](#parallel-streams并行流) + - [Sequential Sort\(串行排序\)](#sequential-sort串行排序) + - [Parallel Sort\(并行排序\)](#parallel-sort并行排序) + - [Maps](#maps) + - [Data API\(日期相关API\)](#data-api日期相关api) + - [Clock](#clock) + - [Timezones\(时区\)](#timezones时区) + - [LocalTime\(本地时间\)](#localtime本地时间) + - [LocalDate\(本地日期\)](#localdate本地日期) + - [LocalDateTime\(本地日期时间\)](#localdatetime本地日期时间) + - [Annotations\(注解\)](#annotations注解) + - [Whete to go from here?](#whete-to-go-from-here) + + + + +# Java 8 Tutorial + +欢迎阅读我对Java 8的介绍。本教程将逐步指导您完成所有新语言功能。 在简短的代码示例的基础上,您将学习如何使用默认接口方法,lambda表达式,方法引用和可重复注释。 在本文的最后,您将熟悉最新的 API 更改,如流,函数式接口(Functional Interfaces),Map 类的扩展和新的 Date API。 没有大段枯燥的文字,只有一堆注释的代码片段。 + + +### 接口的默认方法(Default Methods for Interfaces) + +Java 8使我们能够通过使用 `default` 关键字向接口添加非抽象方法实现。 此功能也称为[虚拟扩展方法](http://stackoverflow.com/a/24102730)。 + +第一个例子: + +```java +interface Formula{ + + double calculate(int a); + + default double sqrt(int a) { + return Math.sqrt(a); + } + +} +``` + +Formula 接口中除了抽象方法计算接口公式还定义了默认方法 `sqrt`。 实现该接口的类只需要实现抽象方法 `calculate`。 默认方法`sqrt` 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。 + +```java +public class Main { + + public static void main(String[] args) { + // TODO 通过匿名内部类方式访问接口 + Formula formula = new Formula() { + @Override + public double calculate(int a) { + return sqrt(a * 100); + } + }; + + System.out.println(formula.calculate(100)); // 100.0 + System.out.println(formula.sqrt(16)); // 4.0 + + } + +} +``` + + formula 是作为匿名对象实现的。该代码非常容易理解,6行代码实现了计算 `sqrt(a * 100)`。在下一节中,我们将会看到在 Java 8 中实现单个方法对象有一种更好更方便的方法。 + +**译者注:** 不管是抽象类还是接口,都可以通过匿名内部类的方式访问。不能通过抽象类或者接口直接创建对象。对于上面通过匿名内部类方式访问接口,我们可以这样理解:一个内部类实现了接口里的抽象方法并且返回一个内部类对象,之后我们让接口的引用来指向这个对象。 + +### Lambda表达式(Lambda expressions) + +首先看看在老版本的Java中是如何排列字符串的: + +```java +List names = Arrays.asList("peter", "anna", "mike", "xenia"); + +Collections.sort(names, new Comparator() { + @Override + public int compare(String a, String b) { + return b.compareTo(a); + } +}); +``` + +只需要给静态方法` Collections.sort` 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 `sort` 方法。 + +在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式: + +```java +Collections.sort(names, (String a, String b) -> { + return b.compareTo(a); +}); +``` + +可以看出,代码变得更段且更具有可读性,但是实际上还可以写得更短: + +```java +Collections.sort(names, (String a, String b) -> b.compareTo(a)); +``` + +对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字,但是你还可以写得更短点: + +```java +names.sort((a, b) -> b.compareTo(a)); +``` + +List 类本身就有一个 `sort` 方法。并且Java编译器可以自动推导出参数类型,所以你可以不用再写一次类型。接下来我们看看lambda表达式还有什么其他用法。 + +### 函数式接口(Functional Interfaces) + +**译者注:** 原文对这部分解释不太清楚,故做了修改! + +Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。**“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。** 像这样的接口,可以被隐式转换为lambda表达式。`java.lang.Runnable` 与 `java.util.concurrent.Callable` 是函数式接口最典型的两个例子。Java 8增加了一种特殊的注解`@FunctionalInterface`,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用`@FunctionalInterface` 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示 + +![@FunctionalInterface 注解](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/@FunctionalInterface.png) + +示例: + +```java +@FunctionalInterface +public interface Converter { + T convert(F from); +} +``` + +```java + // TODO 将数字字符串转换为整数类型 + Converter converter = (from) -> Integer.valueOf(from); + Integer converted = converter.convert("123"); + System.out.println(converted.getClass()); //class java.lang.Integer +``` + +**译者注:** 大部分函数式接口都不用我们自己写,Java8都给我们实现好了,这些接口都在java.util.function包里。 + +### 方法和构造函数引用(Method and Constructor References) + +前一节中的代码还可以通过静态方法引用来表示: + +```java + Converter converter = Integer::valueOf; + Integer converted = converter.convert("123"); + System.out.println(converted.getClass()); //class java.lang.Integer +``` +Java 8允许您通过`::`关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法: + +```java +class Something { + String startsWith(String s) { + return String.valueOf(s.charAt(0)); + } +} +``` + +```java +Something something = new Something(); +Converter converter = something::startsWith; +String converted = converter.convert("Java"); +System.out.println(converted); // "J" +``` + +接下来看看构造函数是如何使用`::`关键字来引用的,首先我们定义一个包含多个构造函数的简单类: + +```java +class Person { + String firstName; + String lastName; + + Person() {} + + Person(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } +} +``` +接下来我们指定一个用来创建Person对象的对象工厂接口: + +```java +interface PersonFactory

{ + P create(String firstName, String lastName); +} +``` + +这里我们使用构造函数引用来将他们关联起来,而不是手动实现一个完整的工厂: + +```java +PersonFactory personFactory = Person::new; +Person person = personFactory.create("Peter", "Parker"); +``` +我们只需要使用 `Person::new` 来获取Person类构造函数的引用,Java编译器会自动根据`PersonFactory.create`方法的参数类型来选择合适的构造函数。 + +### Lamda 表达式作用域(Lambda Scopes) + +#### 访问局部变量 + +我们可以直接在 lambda 表达式中访问外部的局部变量: + +```java +final int num = 1; +Converter stringConverter = + (from) -> String.valueOf(from + num); + +stringConverter.convert(2); // 3 +``` + +但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确: + +```java +int num = 1; +Converter stringConverter = + (from) -> String.valueOf(from + num); + +stringConverter.convert(2); // 3 +``` + +不过这里的 num 必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译: + +```java +int num = 1; +Converter stringConverter = + (from) -> String.valueOf(from + num); +num = 3;//在lambda表达式中试图修改num同样是不允许的。 +``` + +#### 访问字段和静态变量 + +与局部变量相比,我们对lambda表达式中的实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。 + +```java +class Lambda4 { + static int outerStaticNum; + int outerNum; + + void testScopes() { + Converter stringConverter1 = (from) -> { + outerNum = 23; + return String.valueOf(from); + }; + + Converter stringConverter2 = (from) -> { + outerStaticNum = 72; + return String.valueOf(from); + }; + } +} +``` + +#### 访问默认接口方法 + +还记得第一节中的 formula 示例吗? `Formula` 接口定义了一个默认方法`sqrt`,可以从包含匿名对象的每个 formula 实例访问该方法。 这不适用于lambda表达式。 + +无法从 lambda 表达式中访问默认方法,故以下代码无法编译: + +```java +Formula formula = (a) -> sqrt(a * 100); +``` + +### 内置函数式接口(Built-in Functional Interfaces) + +JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: `Comparator` 或`Runnable`,这些接口都增加了`@FunctionalInterface`注解以便能用在 lambda 表达式上。 + +但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 [Google Guava](https://code.google.com/p/guava-libraries/) 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。 + +#### Predicates + +Predicate 接口是只有一个参数的返回布尔类型值的 **断言型** 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非): + +**译者注:** Predicate 接口源码如下 + +```java +package java.util.function; +import java.util.Objects; + +@FunctionalInterface +public interface Predicate { + + // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断. + boolean test(T t); + + //and方法与关系型运算符"&&"相似,两边都成立才返回true + default Predicate and(Predicate other) { + Objects.requireNonNull(other); + return (t) -> test(t) && other.test(t); + } + // 与关系运算符"!"相似,对判断进行取反 + default Predicate negate() { + return (t) -> !test(t); + } + //or方法与关系型运算符"||"相似,两边只要有一个成立就返回true + default Predicate or(Predicate other) { + Objects.requireNonNull(other); + return (t) -> test(t) || other.test(t); + } + // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal). + static Predicate isEqual(Object targetRef) { + return (null == targetRef) + ? Objects::isNull + : object -> targetRef.equals(object); + } +``` + +示例: + +```java +Predicate predicate = (s) -> s.length() > 0; + +predicate.test("foo"); // true +predicate.negate().test("foo"); // false + +Predicate nonNull = Objects::nonNull; +Predicate isNull = Objects::isNull; + +Predicate isEmpty = String::isEmpty; +Predicate isNotEmpty = isEmpty.negate(); +``` + +#### Functions + +Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen): + +**译者注:** Function 接口源码如下 + +```java + +package java.util.function; + +import java.util.Objects; + +@FunctionalInterface +public interface Function { + + //将Function对象应用到输入的参数上,然后返回计算结果。 + R apply(T t); + //将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。 + default Function compose(Function before) { + Objects.requireNonNull(before); + return (V v) -> apply(before.apply(v)); + } + // + default Function andThen(Function after) { + Objects.requireNonNull(after); + return (T t) -> after.apply(apply(t)); + } + + static Function identity() { + return t -> t; + } +} +``` + + + +```java +Function toInteger = Integer::valueOf; +Function backToString = toInteger.andThen(String::valueOf); +backToString.apply("123"); // "123" +``` + +#### Suppliers + +Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。 + +```java +Supplier personSupplier = Person::new; +personSupplier.get(); // new Person +``` + +#### Consumers + +Consumer 接口表示要对单个输入参数执行的操作。 + +```java +Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName); +greeter.accept(new Person("Luke", "Skywalker")); +``` + +#### Comparators + +Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法: + +```java +Comparator comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); + +Person p1 = new Person("John", "Doe"); +Person p2 = new Person("Alice", "Wonderland"); + +comparator.compare(p1, p2); // > 0 +comparator.reversed().compare(p1, p2); // < 0 +``` + +## Optionals + +Optionals不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。 + +Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。 + +译者注:示例中每个方法的作用已经添加。 + +```java +//of():为非null的值创建一个Optional +Optional optional = Optional.of("bam"); +// isPresent(): 如果值存在返回true,否则返回false +optional.isPresent(); // true +//get():如果Optional有值则将其返回,否则抛出NoSuchElementException +optional.get(); // "bam" +//orElse():如果有值则将其返回,否则返回指定的其它值 +optional.orElse("fallback"); // "bam" +//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理 +optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b" +``` + +推荐阅读:[[Java8]如何正确使用Optional](https://blog.kaaass.net/archives/764) + +## Streams(流) + +`java.util.Stream` 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如` java.util.Collection` 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。 + +首先看看Stream是怎么用,首先创建实例代码的用到的数据List: + +```java +List stringCollection = new ArrayList<>(); +stringCollection.add("ddd2"); +stringCollection.add("aaa2"); +stringCollection.add("bbb1"); +stringCollection.add("aaa1"); +stringCollection.add("bbb3"); +stringCollection.add("ccc"); +stringCollection.add("bbb2"); +stringCollection.add("ddd1"); +``` + +Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作: + +### Filter(过滤) + +过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于**中间操作**,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。 + +```java + // 测试 Filter(过滤) + stringList + .stream() + .filter((s) -> s.startsWith("a")) + .forEach(System.out::println);//aaa2 aaa1 +``` + +forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。 + +### Sorted(排序) + +排序是一个 **中间操作**,返回的是排序好后的 Stream。**如果你不指定一个自定义的 Comparator 则会使用默认排序。** + +```java + // 测试 Sort (排序) + stringList + .stream() + .sorted() + .filter((s) -> s.startsWith("a")) + .forEach(System.out::println);// aaa1 aaa2 +``` + +需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的: + +```java + System.out.println(stringList);// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1 +``` + +### Map(映射) + +中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。 + +下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。 + +```java + // 测试 Map 操作 + stringList + .stream() + .map(String::toUpperCase) + .sorted((a, b) -> b.compareTo(a)) + .forEach(System.out::println);// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1" +``` + + + +### Match(匹配) + +Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 **最终操作** ,并返回一个 boolean 类型的值。 + +```java + // 测试 Match (匹配)操作 + boolean anyStartsWithA = + stringList + .stream() + .anyMatch((s) -> s.startsWith("a")); + System.out.println(anyStartsWithA); // true + + boolean allStartsWithA = + stringList + .stream() + .allMatch((s) -> s.startsWith("a")); + + System.out.println(allStartsWithA); // false + + boolean noneStartsWithZ = + stringList + .stream() + .noneMatch((s) -> s.startsWith("z")); + + System.out.println(noneStartsWithZ); // true +``` + + + +### Count(计数) + +计数是一个 **最终操作**,返回Stream中元素的个数,**返回值类型是 long**。 + +```java + //测试 Count (计数)操作 + long startsWithB = + stringList + .stream() + .filter((s) -> s.startsWith("b")) + .count(); + System.out.println(startsWithB); // 3 +``` + +### Reduce(规约) + +这是一个 **最终操作** ,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规约后的结果是通过Optional 接口表示的: + +```java + //测试 Reduce (规约)操作 + Optional reduced = + stringList + .stream() + .sorted() + .reduce((s1, s2) -> s1 + "#" + s2); + + reduced.ifPresent(System.out::println);//aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2 +``` + + + +**译者注:** 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于`Integer sum = integers.reduce(0, (a, b) -> a+b);`也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。 + +```java +// 字符串连接,concat = "ABCD" +String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); +// 求最小值,minValue = -3.0 +double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); +// 求和,sumValue = 10, 有起始值 +int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum); +// 求和,sumValue = 10, 无起始值 +sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get(); +// 过滤,字符串连接,concat = "ace" +concat = Stream.of("a", "B", "c", "D", "e", "F"). + filter(x -> x.compareTo("Z") > 0). + reduce("", String::concat); +``` + +上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。更多内容查看: [IBM:Java 8 中的 Streams API 详解](https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html) + +## Parallel Streams(并行流) + +前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。 + +下面的例子展示了是如何通过并行Stream来提升性能: + +首先我们创建一个没有重复元素的大表: + +```java +int max = 1000000; +List values = new ArrayList<>(max); +for (int i = 0; i < max; i++) { + UUID uuid = UUID.randomUUID(); + values.add(uuid.toString()); +} +``` + +我们分别用串行和并行两种方式对其进行排序,最后看看所用时间的对比。 + +### Sequential Sort(串行排序) + +```java +//串行排序 +long t0 = System.nanoTime(); +long count = values.stream().sorted().count(); +System.out.println(count); + +long t1 = System.nanoTime(); + +long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); +System.out.println(String.format("sequential sort took: %d ms", millis)); +``` + +``` +1000000 +sequential sort took: 709 ms//串行排序所用的时间 +``` + +### Parallel Sort(并行排序) + +```java +//并行排序 +long t0 = System.nanoTime(); + +long count = values.parallelStream().sorted().count(); +System.out.println(count); + +long t1 = System.nanoTime(); + +long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); +System.out.println(String.format("parallel sort took: %d ms", millis)); + +``` + +```java +1000000 +parallel sort took: 475 ms//串行排序所用的时间 +``` + +上面两个代码几乎是一样的,但是并行版的快了 50% 左右,唯一需要做的改动就是将 `stream()` 改为`parallelStream()`。 + +## Maps + +前面提到过,Map 类型不支持 streams,不过Map提供了一些新的有用的方法来处理一些日常任务。Map接口本身没有可用的 `stream()`方法,但是你可以在键,值上创建专门的流或者通过 `map.keySet().stream()`,`map.values().stream()`和`map.entrySet().stream()`。 + +此外,Maps 支持各种新的和有用的方法来执行常见任务。 + +```java +Map map = new HashMap<>(); + +for (int i = 0; i < 10; i++) { + map.putIfAbsent(i, "val" + i); +} + +map.forEach((id, val) -> System.out.println(val));//val0 val1 val2 val3 val4 val5 val6 val7 val8 val9 +``` + +`putIfAbsent` 阻止我们在null检查时写入额外的代码;`forEach`接受一个 consumer 来对 map 中的每个元素操作。 + +此示例显示如何使用函数在 map 上计算代码: + +```java +map.computeIfPresent(3, (num, val) -> val + num); +map.get(3); // val33 + +map.computeIfPresent(9, (num, val) -> null); +map.containsKey(9); // false + +map.computeIfAbsent(23, num -> "val" + num); +map.containsKey(23); // true + +map.computeIfAbsent(3, num -> "bam"); +map.get(3); // val33 +``` + +接下来展示如何在Map里删除一个键值全都匹配的项: + +```java +map.remove(3, "val3"); +map.get(3); // val33 +map.remove(3, "val33"); +map.get(3); // null +``` + +另外一个有用的方法: + +```java +map.getOrDefault(42, "not found"); // not found +``` + +对Map的元素做合并也变得很容易了: + +```java +map.merge(9, "val9", (value, newValue) -> value.concat(newValue)); +map.get(9); // val9 +map.merge(9, "concat", (value, newValue) -> value.concat(newValue)); +map.get(9); // val9concat +``` + +Merge 做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。 + +## Data API(日期相关API) + +Java 8在 `java.time` 包下包含一个全新的日期和时间API。新的Date API与Joda-Time库相似,但它们不一样。以下示例涵盖了此新 API 的最重要部分。译者对这部分内容参考相关书籍做了大部分修改。 + +**译者注(总结):** + +- Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示,`Instant` 类也可以用来创建旧版本的`java.util.Date` 对象。 + +- 在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类`ZoneId`(在`java.time`包中)表示一个区域标识符。 它有一个名为`getAvailableZoneIds`的静态方法,它返回所有区域标识符。 + +- jdk1.8中新增了 LocalDate 与 LocalDateTime等类来解决日期处理方法,同时引入了一个新的类DateTimeFormatter 来解决日期格式化问题。可以使用Instant代替 Date,LocalDateTime代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。 + + + +### Clock + +Clock 类提供了访问当前日期和时间的方法,Clock 是时区敏感的,可以用来取代 `System.currentTimeMillis()` 来获取当前的微秒数。某一个特定的时间点也可以使用 `Instant` 类来表示,`Instant` 类也可以用来创建旧版本的`java.util.Date` 对象。 + +```java +Clock clock = Clock.systemDefaultZone(); +long millis = clock.millis(); +System.out.println(millis);//1552379579043 +Instant instant = clock.instant(); +System.out.println(instant); +Date legacyDate = Date.from(instant); //2019-03-12T08:46:42.588Z +System.out.println(legacyDate);//Tue Mar 12 16:32:59 CST 2019 +``` + +### Timezones(时区) + +在新API中时区使用 ZoneId 来表示。时区可以很方便的使用静态方法of来获取到。 抽象类`ZoneId`(在`java.time`包中)表示一个区域标识符。 它有一个名为`getAvailableZoneIds`的静态方法,它返回所有区域标识符。 + +```java +//输出所有区域标识符 +System.out.println(ZoneId.getAvailableZoneIds()); + +ZoneId zone1 = ZoneId.of("Europe/Berlin"); +ZoneId zone2 = ZoneId.of("Brazil/East"); +System.out.println(zone1.getRules());// ZoneRules[currentStandardOffset=+01:00] +System.out.println(zone2.getRules());// ZoneRules[currentStandardOffset=-03:00] +``` + +### LocalTime(本地时间) + +LocalTime 定义了一个没有时区信息的时间,例如 晚上10点或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差: + +```java +LocalTime now1 = LocalTime.now(zone1); +LocalTime now2 = LocalTime.now(zone2); +System.out.println(now1.isBefore(now2)); // false + +long hoursBetween = ChronoUnit.HOURS.between(now1, now2); +long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); + +System.out.println(hoursBetween); // -3 +System.out.println(minutesBetween); // -239 +``` + +LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串. + +```java +LocalTime late = LocalTime.of(23, 59, 59); +System.out.println(late); // 23:59:59 +DateTimeFormatter germanFormatter = + DateTimeFormatter + .ofLocalizedTime(FormatStyle.SHORT) + .withLocale(Locale.GERMAN); + +LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); +System.out.println(leetTime); // 13:37 +``` + +### LocalDate(本地日期) + +LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。 + +```java +LocalDate today = LocalDate.now();//获取现在的日期 +System.out.println("今天的日期: "+today);//2019-03-12 +LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); +System.out.println("明天的日期: "+tomorrow);//2019-03-13 +LocalDate yesterday = tomorrow.minusDays(2); +System.out.println("昨天的日期: "+yesterday);//2019-03-11 +LocalDate independenceDay = LocalDate.of(2019, Month.MARCH, 12); +DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); +System.out.println("今天是周几:"+dayOfWeek);//TUESDAY +``` + +从字符串解析一个 LocalDate 类型和解析 LocalTime 一样简单,下面是使用 `DateTimeFormatter` 解析字符串的例子: + +```java + String str1 = "2014==04==12 01时06分09秒"; + // 根据需要解析的日期、时间字符串定义解析所用的格式器 + DateTimeFormatter fomatter1 = DateTimeFormatter + .ofPattern("yyyy==MM==dd HH时mm分ss秒"); + + LocalDateTime dt1 = LocalDateTime.parse(str1, fomatter1); + System.out.println(dt1); // 输出 2014-04-12T01:06:09 + + String str2 = "2014$$$四月$$$13 20小时"; + DateTimeFormatter fomatter2 = DateTimeFormatter + .ofPattern("yyy$$$MMM$$$dd HH小时"); + LocalDateTime dt2 = LocalDateTime.parse(str2, fomatter2); + System.out.println(dt2); // 输出 2014-04-13T20:00 + +``` + +再来看一个使用 `DateTimeFormatter` 格式化日期的示例 + +```java +LocalDateTime rightNow=LocalDateTime.now(); +String date=DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(rightNow); +System.out.println(date);//2019-03-12T16:26:48.29 +DateTimeFormatter formatter=DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss"); +System.out.println(formatter.format(rightNow));//2019-03-12 16:26:48 +``` + +### LocalDateTime(本地日期时间) + +LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime 和 LocalTime还有 LocalDate 一样,都是不可变的。LocalDateTime 提供了一些能访问具体字段的方法。 + +```java +LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); + +DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); +System.out.println(dayOfWeek); // WEDNESDAY + +Month month = sylvester.getMonth(); +System.out.println(month); // DECEMBER + +long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); +System.out.println(minuteOfDay); // 1439 +``` + +只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的`java.util.Date`。 + +```java +Instant instant = sylvester + .atZone(ZoneId.systemDefault()) + .toInstant(); + +Date legacyDate = Date.from(instant); +System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014 +``` + +格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式: + +```java +DateTimeFormatter formatter = + DateTimeFormatter + .ofPattern("MMM dd, yyyy - HH:mm"); +LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter); +String string = formatter.format(parsed); +System.out.println(string); // Nov 03, 2014 - 07:13 +``` + +和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。 +关于时间日期格式的详细信息在[这里](https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)。 + +## Annotations(注解) + +在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。 +首先定义一个包装类Hints注解用来放置一组具体的Hint注解: + +```java +@interface Hints { + Hint[] value(); +} +@Repeatable(Hints.class) +@interface Hint { + String value(); +} +``` + +Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下`@Repeatable`即可。 + +例 1: 使用包装类当容器来存多个注解(老方法) + +```java +@Hints({@Hint("hint1"), @Hint("hint2")}) +class Person {} +``` + +例 2:使用多重注解(新方法) + +```java +@Hint("hint1") +@Hint("hint2") +class Person {} +``` + +第二个例子里java编译器会隐性的帮你定义好@Hints注解,了解这一点有助于你用反射来获取这些信息: + +```java +Hint hint = Person.class.getAnnotation(Hint.class); +System.out.println(hint); // null +Hints hints1 = Person.class.getAnnotation(Hints.class); +System.out.println(hints1.value().length); // 2 + +Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); +System.out.println(hints2.length); // 2 +``` + +即便我们没有在 `Person`类上定义 `@Hints`注解,我们还是可以通过 `getAnnotation(Hints.class) `来获取 `@Hints`注解,更加方便的方法是使用 `getAnnotationsByType` 可以直接获取到所有的`@Hint`注解。 +另外Java 8的注解还增加到两种新的target上了: + +```java +@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) +@interface MyAnnotation {} +``` + + + +## Whete to go from here? + +关于Java 8的新特性就写到这了,肯定还有更多的特性等待发掘。JDK 1.8里还有很多很有用的东西,比如`Arrays.parallelSort`, `StampedLock`和`CompletableFuture`等等。 + diff --git "a/Java/What's New in JDK8/Lambda\350\241\250\350\276\276\345\274\217.md" "b/Java/What's New in JDK8/Lambda\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 00000000000..359c4714473 --- /dev/null +++ "b/Java/What's New in JDK8/Lambda\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,235 @@ +JDK8--Lambda表达式 +=== +## 1.什么是Lambda表达式 +**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递** + +## 2.Lambda表达式语法 +```(参数列表) -> {执行代码块}``` +
参数列表可以为空```()->{}``` +
可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值. +
在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}``` +
(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言) + +## 3.函数式接口 +在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```
+**函数式接口指的是有且只有一个抽象(abstract)方法的接口**
+当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子: +
+```java + Thread thread = new Thread(() -> { + System.out.println("This is JDK8's Lambda!"); + }); +``` +这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的? +```java +public Thread(Runnable target) {...} + +/** + * Runnable Interface + */ +@FunctionalInterface +public interface Runnable { + public abstract void run(); +} +``` +到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于 +```java + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + System.out.println("Anonymous class"); + } + }); +``` +也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都 +由编译器帮我们推断,因此可以减少很多代码量)。 +
Lambda也可以这样用 : +```java + Runnable runnable = () -> {...}; +``` +其实这和上面的用法没有什么本质上的区别。 +
至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范, +目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了, +因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]() +
+除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口 +比如(当然不止这些): +- ```java.util.concurrent.Callable``` +- ```java.util.Comparator``` +- ```java.io.FileFilter``` +
**关于JDK中的预定义的函数式接口** + +- JDK在```java.util.function```下预定义了很多函数式接口 + - ```Function {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。 + - ```Consumer {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。 + - ```Predicate {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。 + - ```Supplier {T get();} 提供者(工厂)``` 返回一个T对象。 + - 其他的跟上面的相似,大家可以看一下function包下的具体接口。 +## 4.变量作用域 +```java +public class VaraibleHide { + @FunctionalInterface + interface IInner { + void printInt(int x); + } + public static void main(String[] args) { + int x = 20; + IInner inner = new IInner() { + int x = 10; + @Override + public void printInt(int x) { + System.out.println(x); + } + }; + inner.printInt(30); + + inner = (s) -> { + //Variable used in lambda expression should be final or effectively final + //!int x = 10; + //!x= 50; error + System.out.print(x); + }; + inner.printInt(30); + } +} +输出 : +30 +20 +``` +对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。 +
lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值, +同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该 +不难理解。 +## 5.方法引用 +**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。** +
下面的例子会打印list中的每个元素: +```java +List list = new ArrayList<>(); + for (int i = 0; i < 10; ++i) { + list.add(i); + } + list.forEach(System.out::println); +``` +其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para);}``` +
我们看一下List#forEach方法 ```default void forEach(Consumer action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口 +```java +@FunctionalInterface +public interface Consumer { + void accept(T t); +``` +大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。 +
我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用 +```java +public class MethodReference { + public static void main(String[] args) { + List list = new ArrayList<>(); + for (int i = 0; i < 10; ++i) { + list.add(i); + } + list.forEach(MethodReference::myPrint); + } + + static void myPrint(int i) { + System.out.print(i + ", "); + } +} + +输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +``` +可以看到,我们自己定义的方法也可以当做方法引用。 +
到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。 +- 方法引用 + - 类名::静态方法名 + - 类名::实例方法名 + - 类名::new (构造方法引用) + - 实例名::实例方法名 +可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种 +方法引用的使用方法。
+**类名::实例方法名**
+先来看一段代码 +```java + String[] strings = new String[10]; + Arrays.sort(strings, String::compareToIgnoreCase); +``` +**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**
+我们看一下`Arrays#sort`方法`public static void sort(T[] a, Comparator c)`, +可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下 +`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它 +的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样? +
是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是 +instance.method(x)中的instance, +它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的 +参数。我们再举一个自己实现的例子: +```java +public class MethodReference { + static Random random = new Random(47); + public static void main(String[] args) { + MethodReference[] methodReferences = new MethodReference[10]; + Arrays.sort(methodReferences, MethodReference::myCompare); + } + int myCompare(MethodReference o) { + return random.nextInt(2) - 1; + } +} +``` +上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。 +
**构造器引用**
+构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口 +```java + //compile error : String is not a functional interface + String str = String::new; +``` +下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。 +```java + interface IFunctional { + T func(); +} + +public class ConstructorReference { + + public ConstructorReference() { + } + + public static void main(String[] args) { + Supplier supplier0 = () -> new ConstructorReference(); + Supplier supplier1 = ConstructorReference::new; + IFunctional functional = () -> new ConstructorReference(); + IFunctional functional1 = ConstructorReference::new; + } +} +``` +下面是一个JDK官方的例子 +```java + public static , DEST extends Collection> + DEST transferElements( + SOURCE sourceCollection, + Supplier collectionFactory) { + + DEST result = collectionFactory.get(); + for (T t : sourceCollection) { + result.add(t); + } + return result; + } + + ... + + Set rosterSet = transferElements( + roster, HashSet::new); +``` + +**实例::实例方法** +
+其实开始那个例子就是一个实例::实例方法的引用 +```java +List list = new ArrayList<>(); + for (int i = 0; i < 10; ++i) { + list.add(i); + } + list.forEach(System.out::println); +``` +其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。 +## 总结 +Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量, +但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码, +所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。 diff --git a/Java/What's New in JDK8/README.md b/Java/What's New in JDK8/README.md new file mode 100644 index 00000000000..fa71e907410 --- /dev/null +++ b/Java/What's New in JDK8/README.md @@ -0,0 +1,556 @@ +JDK8新特性总结 +====== +总结了部分JDK8新特性,另外一些新特性可以通过Oracle的官方文档查看,毕竟是官方文档,各种新特性都会介绍,有兴趣的可以去看。
+[Oracle官方文档:What's New in JDK8](https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html) +----- +- [Java语言特性](#JavaProgrammingLanguage) + - [Lambda表达式是一个新的语言特性,已经在JDK8中加入。它是一个可以传递的代码块,你也可以把它们当做方法参数。 + Lambda表达式允许您更紧凑地创建单虚方法接口(称为功能接口)的实例。](#LambdaExpressions) + + - [方法引用为已经存在的具名方法提供易于阅读的Lambda表达式](#MethodReferences) + + - [默认方法允许将新功能添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。](#DefaultMethods) + + - [改进的类型推断。](#ImprovedTypeInference) + + - [方法参数反射(通过反射获得方法参数信息)](#MethodParameterReflection) + +- [流(stream)](#stream) + - [新java.util.stream包中的类提供Stream API以支持对元素流的功能样式操作。流(stream)和I/O里的流不是同一个概念 + ,使用stream API可以更方便的操作集合。]() + +- [国际化]() + - 待办 +- 待办 +___ + + + + + + + +##              Lambda表达式 +### 1.什么是Lambda表达式 +**Lambda表达式实质上是一个可传递的代码块,Lambda又称为闭包或者匿名函数,是函数式编程语法,让方法可以像普通参数一样传递** + +### 2.Lambda表达式语法 +```(参数列表) -> {执行代码块}``` +
参数列表可以为空```()->{}``` +
可以加类型声明比如```(String para1, int para2) -> {return para1 + para2;}```我们可以看到,lambda同样可以有返回值. +
在编译器可以推断出类型的时候,可以将类型声明省略,比如```(para1, para2) -> {return para1 + para2;}``` +
(lambda有点像动态类型语言语法。lambda在字节码层面是用invokedynamic实现的,而这条指令就是为了让JVM更好的支持运行在其上的动态类型语言) + +### 3.函数式接口 +在了解Lambda表达式之前,有必要先了解什么是函数式接口```(@FunctionalInterface)```
+**函数式接口指的是有且只有一个抽象(abstract)方法的接口**
+当需要一个函数式接口的对象时,就可以用Lambda表达式来实现,举个常用的例子: +
+```java + Thread thread = new Thread(() -> { + System.out.println("This is JDK8's Lambda!"); + }); +``` +这段代码和函数式接口有啥关系?我们回忆一下,Thread类的构造函数里是不是有一个以Runnable接口为参数的? +```java +public Thread(Runnable target) {...} + +/** + * Runnable Interface + */ +@FunctionalInterface +public interface Runnable { + public abstract void run(); +} +``` +到这里大家可能已经明白了,**Lambda表达式相当于一个匿名类或者说是一个匿名方法**。上面Thread的例子相当于 +```java + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + System.out.println("Anonymous class"); + } + }); +``` +也就是说,上面的lambda表达式相当于实现了这个run()方法,然后当做参数传入(个人感觉可以这么理解,lambda表达式就是一个函数,只不过它的返回值、参数列表都 +由编译器帮我们推断,因此可以减少很多代码量)。 +
Lambda也可以这样用 : +```java + Runnable runnable = () -> {...}; +``` +其实这和上面的用法没有什么本质上的区别。 +
至此大家应该明白什么是函数式接口以及函数式接口和lambda表达式之间的关系了。在JDK8中修改了接口的规范, +目的是为了在给接口添加新的功能时保持向前兼容(个人理解),比如一个已经定义了的函数式接口,某天我们想给它添加新功能,那么就不能保持向前兼容了, +因为在旧的接口规范下,添加新功能必定会破坏这个函数式接口[(JDK8中接口规范)]() +
+除了上面说的Runnable接口之外,JDK中已经存在了很多函数式接口 +比如(当然不止这些): +- ```java.util.concurrent.Callable``` +- ```java.util.Comparator``` +- ```java.io.FileFilter``` +
**关于JDK中的预定义的函数式接口** + +- JDK在```java.util.function```下预定义了很多函数式接口 + - ```Function {R apply(T t);}``` 接受一个T对象,然后返回一个R对象,就像普通的函数。 + - ```Consumer {void accept(T t);}``` 消费者 接受一个T对象,没有返回值。 + - ```Predicate {boolean test(T t);}``` 判断,接受一个T对象,返回一个布尔值。 + - ```Supplier {T get();} 提供者(工厂)``` 返回一个T对象。 + - 其他的跟上面的相似,大家可以看一下function包下的具体接口。 +### 4.变量作用域 +```java +public class VaraibleHide { + @FunctionalInterface + interface IInner { + void printInt(int x); + } + public static void main(String[] args) { + int x = 20; + IInner inner = new IInner() { + int x = 10; + @Override + public void printInt(int x) { + System.out.println(x); + } + }; + inner.printInt(30); + + inner = (s) -> { + //Variable used in lambda expression should be final or effectively final + //!int x = 10; + //!x= 50; error + System.out.print(x); + }; + inner.printInt(30); + } +} +输出 : +30 +20 +``` +对于lambda表达式```java inner = (s) -> {System.out.print(x);};```,变量x并不是在lambda表达式中定义的,像这样并不是在lambda中定义或者通过lambda +的参数列表()获取的变量成为自由变量,它是被lambda表达式捕获的。 +
lambda表达式和内部类一样,对外部自由变量捕获时,外部自由变量必须为final或者是最终变量(effectively final)的,也就是说这个变量初始化后就不能为它赋新值,同时lambda不像内部类/匿名类,lambda表达式与外围嵌套块有着相同的作用域,因此对变量命名的有关规则对lambda同样适用。大家阅读上面的代码对这些概念应该不难理解。 + +### 5.方法引用 +**只需要提供方法的名字,具体的调用过程由Lambda和函数式接口来确定,这样的方法调用成为方法引用。** +
下面的例子会打印list中的每个元素: +```java +List list = new ArrayList<>(); + for (int i = 0; i < 10; ++i) { + list.add(i); + } + list.forEach(System.out::println); +``` +其中```System.out::println```这个就是一个方法引用,等价于Lambda表达式 ```(para)->{System.out.println(para);}``` +
我们看一下List#forEach方法 ```default void forEach(Consumer action)```可以看到它的参数是一个Consumer接口,该接口是一个函数式接口 +```java +@FunctionalInterface +public interface Consumer { + void accept(T t); +``` +大家能发现这个函数接口的方法和```System.out::println```有什么相似的么?没错,它们有着相似的参数列表和返回值。 +
我们自己定义一个方法,看看能不能像标准输出的打印函数一样被调用 +```java +public class MethodReference { + public static void main(String[] args) { + List list = new ArrayList<>(); + for (int i = 0; i < 10; ++i) { + list.add(i); + } + list.forEach(MethodReference::myPrint); + } + + static void myPrint(int i) { + System.out.print(i + ", "); + } +} + +输出: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +``` +可以看到,我们自己定义的方法也可以当做方法引用。 +
到这里大家多少对方法引用有了一定的了解,我们再来说一下方法引用的形式。 +- 方法引用 + - 类名::静态方法名 + - 类名::实例方法名 + - 类名::new (构造方法引用) + - 实例名::实例方法名 +可以看出,方法引用是通过(方法归属名)::(方法名)来调用的。通过上面的例子已经讲解了一个`类名::静态方法名`的使用方法了,下面再依次介绍其余的几种 +方法引用的使用方法。
+**类名::实例方法名**
+先来看一段代码 +```java + String[] strings = new String[10]; + Arrays.sort(strings, String::compareToIgnoreCase); +``` +**上面的String::compareToIgnoreCase等价于(x, y) -> {return x.compareToIgnoreCase(y);}**
+我们看一下`Arrays#sort`方法`public static void sort(T[] a, Comparator c)`, +可以看到第二个参数是一个Comparator接口,该接口也是一个函数式接口,其中的抽象方法是`int compare(T o1, T o2);`,再看一下 +`String#compareToIgnoreCase`方法,`public int compareToIgnoreCase(String str)`,这个方法好像和上面讲方法引用中`类名::静态方法名`不大一样啊,它 +的参数列表和函数式接口的参数列表不一样啊,虽然它的返回值一样? +
是的,确实不一样但是别忘了,String类的这个方法是个实例方法,而不是静态方法,也就是说,这个方法是需要有一个接收者的。所谓接收者就是 +instance.method(x)中的instance, +它是某个类的实例,有的朋友可能已经明白了。上面函数式接口的`compare(T o1, T o2)`中的第一个参数作为了实例方法的接收者,而第二个参数作为了实例方法的 +参数。我们再举一个自己实现的例子: +```java +public class MethodReference { + static Random random = new Random(47); + public static void main(String[] args) { + MethodReference[] methodReferences = new MethodReference[10]; + Arrays.sort(methodReferences, MethodReference::myCompare); + } + int myCompare(MethodReference o) { + return random.nextInt(2) - 1; + } +} +``` +上面的例子可以在IDE里通过编译,大家有兴趣的可以模仿上面的例子自己写一个程序,打印出排序后的结果。 +
**构造器引用**
+构造器引用仍然需要与特定的函数式接口配合使用,并不能像下面这样直接使用。IDE会提示String不是一个函数式接口 +```java + //compile error : String is not a functional interface + String str = String::new; +``` +下面是一个使用构造器引用的例子,可以看出构造器引用可以和这种工厂型的函数式接口一起使用的。 +```java + interface IFunctional { + T func(); +} + +public class ConstructorReference { + + public ConstructorReference() { + } + + public static void main(String[] args) { + Supplier supplier0 = () -> new ConstructorReference(); + Supplier supplier1 = ConstructorReference::new; + IFunctional functional = () -> new ConstructorReference(); + IFunctional functional1 = ConstructorReference::new; + } +} +``` +下面是一个JDK官方的例子 +```java + public static , DEST extends Collection> + DEST transferElements( + SOURCE sourceCollection, + Supplier collectionFactory) { + + DEST result = collectionFactory.get(); + for (T t : sourceCollection) { + result.add(t); + } + return result; + } + + ... + + Set rosterSet = transferElements( + roster, HashSet::new); +``` + +**实例::实例方法** +
+其实开始那个例子就是一个实例::实例方法的引用 +```java +List list = new ArrayList<>(); + for (int i = 0; i < 10; ++i) { + list.add(i); + } + list.forEach(System.out::println); +``` +其中System.out就是一个实例,println是一个实例方法。相信不用再给大家做解释了。 +### 总结 +Lambda表达式是JDK8引入Java的函数式编程语法,使用Lambda需要直接或者间接的与函数式接口配合,在开发中使用Lambda可以减少代码量, +但是并不是说必须要使用Lambda(虽然它是一个很酷的东西)。有些情况下使用Lambda会使代码的可读性急剧下降,并且也节省不了多少代码, +所以在实际开发中还是需要仔细斟酌是否要使用Lambda。和Lambda相似的还有JDK10中加入的var类型推断,同样对于这个特性需要斟酌使用。 + + +___ + + +##              JDK8接口规范 +### 在JDK8中引入了lambda表达式,出现了函数式接口的概念,为了在扩展接口时保持向前兼容性(JDK8之前扩展接口会使得实现了该接口的类必须实现添加的方法,否则会报错。为了保持兼容性而做出妥协的特性还有泛型,泛型也是为了保持兼容性而失去了在一些别的语言泛型拥有的功能),Java接口规范发生了一些改变。 +### 1.JDK8以前的接口规范 +- JDK8以前接口可以定义的变量和方法 + - 所有变量(Field)不论是否显式 的声明为```public static final```,它实际上都是```public static final```的。 + - 所有方法(Method)不论是否显示 的声明为```public abstract```,它实际上都是```public abstract```的。 +```java +public interface AInterfaceBeforeJDK8 { + int FIELD = 0; + void simpleMethod(); +} +``` +以上接口信息反编译以后可以看到字节码信息里Filed是public static final的,而方法是public abstract的,即是你没有显示的去声明它。 +```java +{ + public static final int FIELD; + descriptor: I + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + public abstract void simpleMethod(); + descriptor: ()V + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT +} +``` +### 2.JDK8之后的接口规范 +- JDK8之后接口可以定义的变量和方法 + - 变量(Field)仍然必须是 ```java public static final```的 + - 方法(Method)除了可以是public abstract之外,还可以是public static或者是default(相当于仅public修饰的实例方法)的。 +从以上改变不难看出,修改接口的规范主要是为了能在扩展接口时保持向前兼容。 +
下面是一个JDK8之后的接口例子 +```java +public interface AInterfaceInJDK8 { + int simpleFiled = 0; + static int staticField = 1; + + public static void main(String[] args) { + } + static void staticMethod(){} + + default void defaultMethod(){} + + void simpleMethod() throws IOException; + +} +``` +进行反编译(去除了一些没用信息) +```java +{ + public static final int simpleFiled; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + + public static final int staticField; + flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL + + public static void main(java.lang.String[]); + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public static void staticMethod(); + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public void defaultMethod(); + flags: (0x0001) ACC_PUBLIC + + public abstract void simpleMethod() throws java.io.IOException; + flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT + Exceptions: + throws java.io.IOException +} +``` +可以看到 default关键字修饰的方法是像实例方法(就是普通类中定义的普通方法)一样定义的,所以我们来定义一个只有default方法的接口并且实现一下这个接口试一 +试。 +```java +interface Default { + default int defaultMethod() { + return 4396; + } +} + +public class DefaultMethod implements Default { + public static void main(String[] args) { + DefaultMethod defaultMethod = new DefaultMethod(); + System.out.println(defaultMethod.defaultMethod()); + //compile error : Non-static method 'defaultMethod()' cannot be referenced from a static context + //! DefaultMethod.defaultMethod(); + } +} +``` +可以看到default方法确实像实例方法一样,必须有实例对象才能调用,并且子类在实现接口时,可以不用实现default方法,也可以选择覆盖该方法。 +这有点像子类继承父类实例方法。 +
+接口静态方法就像是类静态方法,唯一的区别是**接口静态方法只能通过接口名调用,而类静态方法既可以通过类名调用也可以通过实例调用** +```java +interface Static { + static int staticMethod() { + return 4396; + } +} + ... main(String...args) + //!compile error: Static method may be invoked on containing interface class only + //!aInstanceOfStatic.staticMethod(); + ... +``` +另一个问题是多继承问题,大家知道Java中类是不支持多继承的,但是接口是多继承和多实现(implements后跟多个接口)的, +那么如果一个接口继承另一个接口,两个接口都有同名的default方法会怎么样呢?答案是会像类继承一样覆写(@Override),以下代码在IDE中可以顺利编译 +```java +interface Default { + default int defaultMethod() { + return 4396; + } +} +interface Default2 extends Default { + @Override + default int defaultMethod() { + return 9527; + } +} +public class DefaultMethod implements Default,Default2 { + public static void main(String[] args) { + DefaultMethod defaultMethod = new DefaultMethod(); + System.out.println(defaultMethod.defaultMethod()); + } +} + +输出 : 9527 +``` +出现上面的情况时,会优先找继承树上近的方法,类似于“短路优先”。 +
+那么如果一个类实现了两个没有继承关系的接口,且这两个接口有同名方法的话会怎么样呢?IDE会要求你重写这个冲突的方法,让你自己选择去执行哪个方法,因为IDE它还没智能到你不告诉它,它就知道你想执行哪个方法。可以通过```java 接口名.super```指针来访问接口中定义的实例(default)方法。 +```java +interface Default { + default int defaultMethod() { + return 4396; + } +} + +interface Default2 { + default int defaultMethod() { + return 9527; + } +} +//如果不重写 +//compile error : defaults.DefaultMethod inherits unrelated defaults for defaultMethod() from types defaults.Default and defaults.Default2 +public class DefaultMethod implements Default,Default2 { +@Override + public int defaultMethod() { + System.out.println(Default.super.defaultMethod()); + System.out.println(Default2.super.defaultMethod()); + return 996; + } + public static void main(String[] args) { + DefaultMethod defaultMethod = new DefaultMethod(); + System.out.println(defaultMethod.defaultMethod()); + } +} + +运行输出 : +4396 +9527 +996 +``` + + +___ + + +##              改进的类型推断 +### 1.什么是类型推断 +类型推断就像它的字面意思一样,编译器根据你显示声明的已知的信息 推断出你没有显示声明的类型,这就是类型推断。 +看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的: +```java + Map map = new Map(); +``` +而我们平常写的都是这样的: +```java + Map map = new Map<>(); +``` +这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题 +(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。 +(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽) +
+在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译 +错误 +```java + List stringList = new ArrayList<>(); + stringList.add("A"); + //error : addAll(java.util.Collection)in List cannot be applied to (java.util.List) + stringList.addAll(Arrays.asList()); +``` +但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码 +会更少。加强的类型推断还有一个就是用于Lambda表达式了。 +
+大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息, +相信随着Java的进化,这些便利的功能会越来越强大。 + + +____ + + +##              通过反射获得方法的参数信息 +JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里 +也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外 +的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如 +LocalVariableTable,javac就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也需要加上 +-parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该 +参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。 +```java +public class ByteCodeParameters { + public String simpleMethod(String canUGetMyName, Object yesICan) { + return "9527"; + } +} +``` +先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters: +```java + //只截取了部分信息 + public java.lang.String simpleMethod(java.lang.String, java.lang.Object); + descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=3, args_size=3 + 0: ldc #2 // String 9527 + 2: areturn + LineNumberTable: + line 5: 0 + //这个方法的描述到这里就结束了 +``` +接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息: +```java + public java.lang.String simpleMethod(java.lang.String, java.lang.Object); + descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=3, args_size=3 + 0: ldc #2 // String 9527 + 2: areturn + LineNumberTable: + line 8: 0 + MethodParameters: + Name Flags + canUGetMyName + yesICan +``` +可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。 +
下面看一下在Intelj Idea里运行的这个例子,我们试一下通过反射获取方法名 : +```java +public class ByteCodeParameters { + public String simpleMethod(String canUGetMyName, Object yesICan) { + return "9527"; + } + + public static void main(String[] args) throws NoSuchMethodException { + Class clazz = ByteCodeParameters.class; + Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class); + Parameter[] parameters = simple.getParameters(); + for (Parameter p : parameters) { + System.out.println(p.getName()); + } + } +} +输出 : +arg0 +arg1 +``` +???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的settings里的Java Compiler选项,在 +Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自 +己编译一个.class文件放在IDEA的out下,然后再来运行 : +```java +输出 : +canUGetMyName +yesICan +``` +这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档] +(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html) +
+## 总结与补充 +在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework +里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。 + +____ + + + + +___ diff --git "a/Java/What's New in JDK8/\346\224\271\350\277\233\347\232\204\347\261\273\345\236\213\346\216\250\346\226\255.md" "b/Java/What's New in JDK8/\346\224\271\350\277\233\347\232\204\347\261\273\345\236\213\346\216\250\346\226\255.md" new file mode 100644 index 00000000000..b5cff7bb0c0 --- /dev/null +++ "b/Java/What's New in JDK8/\346\224\271\350\277\233\347\232\204\347\261\273\345\236\213\346\216\250\346\226\255.md" @@ -0,0 +1,30 @@ +##              改进的类型推断 +### 1.什么是类型推断 +类型推断就像它的字面意思一样,编译器根据你显示声明的已知的信息 推断出你没有显示声明的类型,这就是类型推断。 +看过《Java编程思想 第四版》的朋友可能还记得里面讲解泛型一章的时候,里面很多例子是下面这样的: +```java + Map map = new Map(); +``` +而我们平常写的都是这样的: +```java + Map map = new Map<>(); +``` +这就是类型推断,《Java编程思想 第四版》这本书出书的时候最新的JDK只有1.6(JDK7推出的类型推断),在Java编程思想里Bruce Eckel大叔还提到过这个问题 +(可能JDK的官方人员看了Bruce Eckel大叔的Thinking in Java才加的类型推断,☺),在JDK7中推出了上面这样的类型推断,可以减少一些无用的代码。 +(Java编程思想到现在还只有第四版,是不是因为Bruce Eckel大叔觉得Java新推出的语言特性“然并卵”呢?/滑稽) +
+在JDK7中,类型推断只有上面例子的那样的能力,即只有在使用**赋值语句**时才能自动推断出泛型参数信息(即<>里的信息),下面的官方文档里的例子在JDK7里会编译 +错误 +```java + List stringList = new ArrayList<>(); + stringList.add("A"); + //error : addAll(java.util.Collection)in List cannot be applied to (java.util.List) + stringList.addAll(Arrays.asList()); +``` +但是上面的代码在JDK8里可以通过,也就说,JDK8里,类型推断不仅可以用于赋值语句,而且可以根据代码中上下文里的信息推断出更多的信息,因此我们需要些的代码 +会更少。加强的类型推断还有一个就是用于Lambda表达式了。 +
+大家其实不必细究类型推断,在日常使用中IDE会自动判断,当IDE自己无法推断出足够的信息时,就需要我们额外做一下工作,比如在<>里添加更多的类型信息, +相信随着Java的进化,这些便利的功能会越来越强大。 + + diff --git "a/Java/What's New in JDK8/\351\200\232\350\277\207\345\217\215\345\260\204\350\216\267\345\276\227\346\226\271\346\263\225\347\232\204\345\217\202\346\225\260\344\277\241\346\201\257.md" "b/Java/What's New in JDK8/\351\200\232\350\277\207\345\217\215\345\260\204\350\216\267\345\276\227\346\226\271\346\263\225\347\232\204\345\217\202\346\225\260\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..a1d91c4b2fe --- /dev/null +++ "b/Java/What's New in JDK8/\351\200\232\350\277\207\345\217\215\345\260\204\350\216\267\345\276\227\346\226\271\346\263\225\347\232\204\345\217\202\346\225\260\344\277\241\346\201\257.md" @@ -0,0 +1,79 @@ +##              通过反射获得方法的参数信息 +JDK8之前 .class文件是不会存储方法参数信息的,因此也就无法通过反射获取该信息(想想反射获取类信息的入口是什么?当然就是Class类了)。即是是在JDK11里 +也不会默认生成这些信息,可以通过在javac加上-parameters参数来让javac生成这些信息(javac就是java编译器,可以把java文件编译成.class文件)。生成额外 +的信息(运行时非必须信息)会消耗内存并且有可能公布敏感信息(某些方法参数比如password,JDK文档里这么说的),并且确实很多信息javac并不会为我们生成,比如 +LocalVariableTable,javac就不会默认生成,需要你加上 -g:vars来强制让编译器生成,同样的,方法参数信息也需要加上 +-parameters来让javac为你在.class文件中生成这些信息,否则运行时反射是无法获取到这些信息的。在讲解Java语言层面的方法之前,先看一下javac加上该 +参数和不加生成的信息有什么区别(不感兴趣想直接看运行代码的可以跳过这段)。下面是随便写的一个类。 +```java +public class ByteCodeParameters { + public String simpleMethod(String canUGetMyName, Object yesICan) { + return "9527"; + } +} +``` +先来不加参数编译和反编译一下这个类javac ByteCodeParameters.java , javap -v ByteCodeParameters: +```java + //只截取了部分信息 + public java.lang.String simpleMethod(java.lang.String, java.lang.Object); + descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=3, args_size=3 + 0: ldc #2 // String 9527 + 2: areturn + LineNumberTable: + line 5: 0 + //这个方法的描述到这里就结束了 +``` +接下来我们加上参数javac -parameters ByteCodeParameters.java 再来看反编译的信息: +```java + public java.lang.String simpleMethod(java.lang.String, java.lang.Object); + descriptor: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String; + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=3, args_size=3 + 0: ldc #2 // String 9527 + 2: areturn + LineNumberTable: + line 8: 0 + MethodParameters: + Name Flags + canUGetMyName + yesICan +``` +可以看到.class文件里多了一个MethodParameters信息,这就是参数的名字,可以看到默认是不保存的。 +
下面看一下在Intelj Idea里运行的这个例子,我们试一下通过反射获取方法名 : +```java +public class ByteCodeParameters { + public String simpleMethod(String canUGetMyName, Object yesICan) { + return "9527"; + } + + public static void main(String[] args) throws NoSuchMethodException { + Class clazz = ByteCodeParameters.class; + Method simple = clazz.getDeclaredMethod("simpleMethod", String.class, Object.class); + Parameter[] parameters = simple.getParameters(); + for (Parameter p : parameters) { + System.out.println(p.getName()); + } + } +} +输出 : +arg0 +arg1 +``` +???说好的方法名呢????别急,哈哈。前面说了,默认是不生成参数名信息的,因此我们需要做一些配置,我们找到IDEA的settings里的Java Compiler选项,在 +Additional command line parameters:一行加上-parameters(Eclipse 也是找到Java Compiler选中Stoer information about method parameters),或者自 +己编译一个.class文件放在IDEA的out下,然后再来运行 : +```java +输出 : +canUGetMyName +yesICan +``` +这样我们就通过反射获取到参数信息了。想要了解更多的同学可以自己研究一下 [官方文档] +(https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html) +
+## 总结与补充 +在JDK8之后,可以通过-parameters参数来让编译器生成参数信息然后在运行时通过反射获取方法参数信息,其实在SpringFramework +里面也有一个LocalVariableTableParameterNameDiscoverer对象可以获取方法参数名信息,有兴趣的同学可以自行百度(这个类在打印日志时可能会比较有用吧,个人感觉)。 diff --git "a/Java\347\233\270\345\205\263/synchronized.md" b/Java/synchronized.md similarity index 64% rename from "Java\347\233\270\345\205\263/synchronized.md" rename to Java/synchronized.md index 25126b7a14e..0a1f4f2b073 100644 --- "a/Java\347\233\270\345\205\263/synchronized.md" +++ b/Java/synchronized.md @@ -1,31 +1,109 @@ -本文是对 synchronized 关键字使用、底层原理、JDK1.6之后的底层优化以及和ReenTrantLock对比做的总结。如果没有学过 synchronized 关键字使用的话,阅读起来可能比较费力。两篇比较基础的讲解 synchronized 关键字的文章: -- [《Java多线程学习(二)synchronized关键字(1)》](https://blog.csdn.net/qq_34337272/article/details/79655194) -- [《Java多线程学习(二)synchronized关键字(2)》](https://blog.csdn.net/qq_34337272/article/details/79670775) -# synchronized 关键字的总结 +![Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/%E4%BA%8C%20%20Synchronized%20%E5%85%B3%E9%94%AE%E5%AD%97%E4%BD%BF%E7%94%A8%E3%80%81%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E3%80%81JDK1.6%20%E4%B9%8B%E5%90%8E%E7%9A%84%E5%BA%95%E5%B1%82%E4%BC%98%E5%8C%96%E4%BB%A5%E5%8F%8A%20%E5%92%8CReenTrantLock%20%E7%9A%84%E5%AF%B9%E6%AF%94.png) -## synchronized关键字最主要的三种使用方式的总结 +### synchronized关键字最主要的三种使用方式的总结 - **修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁** -- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 +- **修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁** 。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,**因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁**。 - **修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。** 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。另外需要注意的是:尽量不要使用 synchronized(String a) 因为JVM中,字符串常量池具有缓冲功能! -## synchronized 关键字底层实现原理总结 +下面我已一个常见的面试题为例讲解一下 synchronized 关键字的具体使用。 -- **synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 -- **synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。** 在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 +面试中面试官经常会说:“单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!” - > 所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统, -## synchronized关键字底层优化总结 +**双重校验锁实现对象单例(线程安全)** -JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 +```java +public class Singleton { + + private volatile static Singleton uniqueInstance; + + private Singleton() { + } + + public static Singleton getUniqueInstance() { + //先判断对象是否已经实例过,没有实例化过才进入加锁代码 + if (uniqueInstance == null) { + //类对象加锁 + synchronized (Singleton.class) { + if (uniqueInstance == null) { + uniqueInstance = new Singleton(); + } + } + } + return uniqueInstance; + } +} +``` +另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。 + +uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执行: + +1. 为 uniqueInstance 分配内存空间 +2. 初始化 uniqueInstance +3. 将 uniqueInstance 指向分配的内存地址 + +但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance,但此时 uniqueInstance 还未被初始化。 + +使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 + + +###synchronized 关键字底层原理总结 + + + +**synchronized 关键字底层原理属于 JVM 层面。** + +**① synchronized 同步语句块的情况** + +```java +public class SynchronizedDemo { + public void method() { + synchronized (this) { + System.out.println("synchronized 代码块"); + } + } +} + +``` + +通过 JDK 自带的 javap 命令查看 SynchronizedDemo 类的相关字节码信息:首先切换到类的对应目录执行 `javac SynchronizedDemo.java` 命令生成编译后的 .class 文件,然后执行`javap -c -s -v -l SynchronizedDemo.class`。 + +![synchronized 关键字原理](https://images.gitbook.cn/abc37c80-d21d-11e8-aab3-09d30029e0d5) + +从上面我们可以看出: + +**synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。** 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。 + +**② synchronized 修饰方法的的情况** + +```java +public class SynchronizedDemo2 { + public synchronized void method() { + System.out.println("synchronized 方法"); + } +} + +``` + +![synchronized 关键字原理](https://images.gitbook.cn/7d407bf0-d21e-11e8-b2d6-1188c7e0dd7e) + +synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。 + + +在 Java 早期版本中,synchronized 属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对synchronized 较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 + + +### JDK1.6 之后的底层优化 + +JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。 锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。 -### 偏向锁 +**①偏向锁** **引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉**。 @@ -33,13 +111,13 @@ JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋 但是对于锁竞争比较激烈的场合,偏向锁就失效了,因为这样场合极有可能每次申请锁的线程都是不相同的,因此这种场合下不应该使用偏向锁,否则会得不偿失,需要注意的是,偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁。 -### 轻量级锁 +**② 轻量级锁** 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。**轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作。** 关于轻量级锁的加锁和解锁的原理可以查看《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版的13章第三节锁优化。 **轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁!** -### 自旋锁和自适应自旋 +**③ 自旋锁和自适应自旋** 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。 @@ -55,29 +133,28 @@ JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋 另外,**在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了**。 -### 锁消除 +**④ 锁消除** 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 -### 锁粗化 +**⑤ 锁粗化** -原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 +原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 -## ReenTrantLock 和 synchronized 关键字的总结 +### Synchronized 和 ReenTrantLock 的对比 -推荐一篇讲解 ReenTrantLock 的使用比较基础的文章:[《Java多线程学习(六)Lock锁的使用》](https://blog.csdn.net/qq_34337272/article/details/79714196) -### 两者都是可重入锁 +**① 两者都是可重入锁** 两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。 -### synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API +**② synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API** synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReenTrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。 -### ReenTrantLock 比 synchronized 增加了一些高级功能 +**③ ReenTrantLock 比 synchronized 增加了一些高级功能** 相比synchronized,ReenTrantLock增加了一些高级功能。主要来说主要有三点:**①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)** @@ -87,28 +164,6 @@ synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团 如果你想使用上述功能,那么选择ReenTrantLock是一个不错的选择。 -### 性能已不是选择标准 - -在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作**。 - - -## 参考 - -- 《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版第13章 -- 《实战Java虚拟机》 -- https://blog.csdn.net/javazejian/article/details/72828483#commentBox -- https://blog.csdn.net/qq838642798/article/details/65441415 -- http://cmsblogs.com/?p=2071 - - - - - - - - - - - - +**④ 性能已不是选择标准** +在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量随线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。**JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作**。 diff --git "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" "b/Java/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" similarity index 81% rename from "Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" rename to "Java/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" index d5ecd9063fe..ac0e8322b67 100644 --- "a/Java\347\233\270\345\205\263/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" +++ "b/Java/\345\217\257\350\203\275\346\230\257\346\212\212Java\345\206\205\345\255\230\345\214\272\345\237\237\350\256\262\347\232\204\346\234\200\346\270\205\346\245\232\347\232\204\344\270\200\347\257\207\346\226\207\347\253\240.md" @@ -1,28 +1,36 @@ +## 写在前面(常见面试题) -## 写在前面(常见面试题) - -### 基本问题: +### 基本问题 - **介绍下 Java 内存区域(运行时数据区)** - **Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)** - **对象的访问定位的两种方式(句柄和直接指针两种方式)** -### 拓展问题: +### 拓展问题 - **String类和常量池** - **8种基本类型的包装类和常量池** -## 1 概述 +## 一 概述 对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不容易出现内存泄漏和内存溢出问题。正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。 -## 2 运行时数据区域 -Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。 -![运行时数据区域](https://user-gold-cdn.xitu.io/2018/4/27/16306a34cd8a4354?w=513&h=404&f=png&s=132068) -这些组成部分一些事线程私有的,其他的则是线程共享的。 +## 二 运行时数据区域 +Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。JDK. 1.8 和之前的版本略有不同,下面会介绍到。 + +**JDK 1.8之前:** + +

+ +
+**JDK 1.8 :** + +
+ +
**线程私有的:** @@ -34,7 +42,7 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成 - 堆 - 方法区 -- 直接内存 +- 直接内存(非运行时数据区的一部分) ### 2.1 程序计数器 @@ -47,11 +55,11 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成 1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。 2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。 -**注意:程序计数器是唯一一个不会出现OutOfMemoryError的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。** +**注意:程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。** ### 2.2 Java 虚拟机栈 -**与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型。** +**与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。** **Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。** (实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。) @@ -64,6 +72,17 @@ Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成 Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。 +**扩展:那么方法/函数如何调用?** + +Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入Java栈,每一个函数调用结束后,都会有一个栈帧被弹出。 + +Java方法有两种返回方式: + +1. return 语句。 +2. 抛出异常。 + +不管哪种返回方式都会导致栈帧被弹出。 + ### 2.3 本地方法栈 和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。 @@ -75,15 +94,13 @@ Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟 ### 2.4 堆 Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。**此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。** -Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:在细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** +Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** -![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946) +
+ +
-**在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。** - -推荐阅读: - -- 《Java8内存模型—永久代(PermGen)和元空间(Metaspace)》:[http://www.cnblogs.com/paddix/p/5309550.html](http://www.cnblogs.com/paddix/p/5309550.html) +上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 ### 2.5 方法区 @@ -91,9 +108,11 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Ga HotSpot 虚拟机中方法区也常被称为 **“永久代”**,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。 +**相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。** +JDK 1.8 的时候,方法区被彻底移除了(JDK1.7就已经开始了),取而代之是元空间,元空间使用的是直接内存。 -**相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。** +我们可以使用参数: `-XX:MetaspaceSize ` 来指定元数据区的大小。与永久区很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。 ### 2.6 运行时常量池 @@ -103,22 +122,20 @@ HotSpot 虚拟机中方法区也常被称为 **“永久代”**,本质上两 **JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。** - -推荐阅读: - -- 《Java 中几种常量池的区分》: [https://blog.csdn.net/qq_26222859/article/details/73135660](https://blog.csdn.net/qq_26222859/article/details/73135660) +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-14/26038433.jpg) +——图片来源:https://blog.csdn.net/wangbiao007/article/details/78545189 ### 2.7 直接内存 -直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。 +**直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 异常出现。** -JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道(Channel)** 与**缓存区(Buffer)** 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。 +JDK1.4 中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通道(Channel)** 与**缓存区(Buffer)** 的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为**避免了在 Java 堆和 Native 堆之间来回复制数据**。 本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。 -## 3 HotSpot 虚拟机对象探秘 +## 三 HotSpot 虚拟机对象探秘 通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。 ### 3.1 对象的创建 @@ -135,7 +152,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的 ![](https://user-gold-cdn.xitu.io/2018/8/22/16561e59a40a2c3d?w=1426&h=333&f=png&s=26346) - + **内存分配并发问题(补充内容,需要掌握)** 在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全: @@ -155,9 +172,9 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 ### 3.2 对象的内存布局 -在 Hotspot 虚拟机中,对象在内存中的布局可以分为3快区域:**对象头**、**实例数据**和**对齐填充**。 +在 Hotspot 虚拟机中,对象在内存中的布局可以分为3块区域:**对象头**、**实例数据**和**对齐填充**。 -**Hotspot虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希吗、GC分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 +**Hotspot虚拟机的对象头包括两部分信息**,**第一部分用于存储对象自身的自身运行时数据**(哈希码、GC分代年龄、锁状态标志等等),**另一部分是类型指针**,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是那个类的实例。 **实例数据部分是对象真正存储的有效信息**,也是在程序中所定义的各种类型的字段内容。 @@ -169,7 +186,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 1. **句柄:** 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息; ![使用句柄](https://user-gold-cdn.xitu.io/2018/4/27/16306b9573968946?w=786&h=362&f=png&s=109201) -2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对像的布局中就必须考虑如何防止访问类型数据的相关信息,reference 中存储的直接就是对象的地址。 +2. **直接指针:** 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。 ![使用直接指针](https://user-gold-cdn.xitu.io/2018/4/27/16306ba3a41b6b65?w=766&h=353&f=png&s=99172) @@ -199,7 +216,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 **2 String 类型的常量池比较特殊。它的主要使用方法有两种:** - 直接使用双引号声明出来的 String 对象会直接存储在常量池中。 -- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 +- 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方法。String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。 ```java String s1 = new String("计算机"); @@ -207,7 +224,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 String s3 = "计算机"; System.out.println(s2);//计算机 System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象, - System.out.println(s3 == s2);//true,因为两个都是常量池中的String对 + System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象 ``` **3 String 字符串拼接** ```java @@ -231,7 +248,7 @@ JDK1.4中新加入的 **NIO(New Input/Output) 类**,引入了一种基于**通 **验证:** ```java - String s1 = new String("abc");// 堆内存的地值值 + String s1 = new String("abc");// 堆内存的地址值 String s2 = "abc"; System.out.println(s1 == s2);// 输出false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。 System.out.println(s1.equals(s2));// 输出true @@ -321,8 +338,7 @@ i4=i5+i6 true 语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。 - -**参考:** +## 参考 - 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》 - 《实战java虚拟机》 diff --git "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" "b/Java/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" similarity index 97% rename from "Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" rename to "Java/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" index 649d44e1f4f..6ed7bdafb21 100644 --- "a/Java\347\233\270\345\205\263/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" +++ "b/Java/\345\244\232\347\272\277\347\250\213\347\263\273\345\210\227.md" @@ -65,5 +65,5 @@ ### Java多线程学习(八)线程池与Executor 框架 -![本节思维导图](https://user-gold-cdn.xitu.io/2018/5/31/163b4379a605fa18?w=1560&h=752&f=png&s=56361) +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-14/86510659.jpg) diff --git "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" "b/Java/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" similarity index 90% rename from "Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" rename to "Java/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" index 21ed7047dd7..4530f3d3b0e 100644 --- "a/Java\347\233\270\345\205\263/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" +++ "b/Java/\346\220\236\345\256\232JVM\345\236\203\345\234\276\345\233\236\346\224\266\345\260\261\346\230\257\350\277\231\344\271\210\347\256\200\345\215\225.md" @@ -20,7 +20,7 @@ ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/29176325.jpg) -当需要排查各种 内存溢出问题、当垃圾收集称为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 +当需要排查各种 内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 @@ -28,11 +28,17 @@ Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。 -**JDK1.8之前的堆内存示意图:** +Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC堆(Garbage Collected Heap)**.从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。**进一步划分的目的是更好地回收内存,或者更快地分配内存。** + +**堆空间的基本结构:** + +
+ +
+ +上图所示的 eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 -![](https://user-gold-cdn.xitu.io/2018/8/25/16570344a29c3433?w=599&h=250&f=png&s=8946) -从上图可以看出堆内存的分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survior1 区+Survior2 区。值得注意的是,在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。 ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/89294547.jpg) @@ -42,7 +48,7 @@ Java 的自动内存管理主要是针对对象内存的回收和对象内存的 大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC.下面我们来进行实际测试以下。 -在测试之前我们先来看看 **Minor Gc和Full GC 有什么不同呢?** +在测试之前我们先来看看 **Minor GC和Full GC 有什么不同呢?** - **新生代GC(Minor GC)**:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。 - **老年代GC(Major GC/Full GC)**:指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。 @@ -65,7 +71,7 @@ public class GCTest { 添加的参数:`-XX:+PrintGCDetails` ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/10317146.jpg) -运行结果: +运行结果(红色字体描述有误,应该是对应于JDK1.7的永久代): ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28954286.jpg) @@ -76,7 +82,7 @@ allocation2 = new byte[900*1024]; ``` ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg) -**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survior空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。可以执行如下代码验证: +**简单解释一下为什么会出现这种情况:** 因为给allocation2分配内存的时候eden区内存几乎已经被分配完了,我们刚刚讲了当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC.GC期间虚拟机又发现allocation1无法存入Survivor空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放allocation1,所以不会出现Full GC。执行Minor GC后,后面分配的对象如果能够存在eden区的话,还是会在eden区分配内存。可以执行如下代码验证: ```java public class GCTest { @@ -102,7 +108,7 @@ public class GCTest { 为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。 ### 1.3 长期存活的对象将进入老年代 -既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别那些对象应放在新生代,那些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 +既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 @@ -163,13 +169,13 @@ JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用 **2.软引用(SoftReference)** -如果一个对象只具有软引用,那就类似于**可有可物的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 +如果一个对象只具有软引用,那就类似于**可有可无的生活用品**。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA虚拟机就会把这个软引用加入到与之关联的引用队列中。 **3.弱引用(WeakReference)** -如果一个对象只具有弱引用,那就类似于**可有可物的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 +如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 @@ -230,15 +236,15 @@ JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用 ![复制算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/90984624.jpg) ### 3.3 标记-整理算法 -根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一段移动,然后直接清理掉端边界以外的内存。 +根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。 ![标记-整理算法](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-27/94057049.jpg) ### 3.4 分代收集算法 -当前虚拟机的垃圾手机都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 +当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 -**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清楚”或“标记-整理”算法进行垃圾收集。** +**比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。** **延伸面试问题:** HotSpot为什么要分为新生代和老年代? @@ -250,7 +256,7 @@ JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用 **如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。** -虽然我们对各个收集器进行比较,但并非了挑选出一个最好的收集器。因为知道现在位置还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。 +虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为知道现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的HotSpot虚拟机就不会实现那么多不同的垃圾收集器了。 ### 4.1 Serial收集器 @@ -306,7 +312,7 @@ Parallel Scavenge 收集器类似于ParNew 收集器。 **那么它有什么特 ### 4.5 Parallel Old收集器 **Parallel Scavenge收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器。 - + ### 4.6 CMS收集器 **CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。** diff --git "a/Java/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/Java/\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..e3e95860529 --- /dev/null +++ "b/Java/\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,84 @@ +# Java 设计模式 + +下面是自己学习设计模式的时候做的总结,有些是自己的原创文章,有些是网上写的比较好的文章,保存下来细细消化吧! + +**系列文章推荐:** + +## 创建型模式 + +### 创建型模式概述 + +- 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。 +- 创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 + +![创建型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640641afcb7559b?w=491&h=241&f=png&s=51443) + +### 常见创建型模式详解 + +- **单例模式:** [深入理解单例模式——只有一个实例](https://blog.csdn.net/qq_34337272/article/details/80455972) +- **工厂模式:** [深入理解工厂模式——由对象工厂生成对象](https://blog.csdn.net/qq_34337272/article/details/80472071) +- **建造者模式:** [深入理解建造者模式 ——组装复杂的实例](http://blog.csdn.net/qq_34337272/article/details/80540059) +- **原型模式:** [深入理解原型模式 ——通过复制生成实例](https://blog.csdn.net/qq_34337272/article/details/80706444) + +## 结构型模式 + +### 结构型模式概述 + +- **结构型模式(Structural Pattern):** 描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构 +![结构型模式(Structural Pattern)](https://user-gold-cdn.xitu.io/2018/6/16/164064d6b3c205e3?w=719&h=233&f=png&s=270293) +- **结构型模式可以分为类结构型模式和对象结构型模式:** + - 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。 + - 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。 + +![结构型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640655459d766d2?w=378&h=266&f=png&s=59652) + +### 常见结构型模式详解 + +- **适配器模式:** + - [深入理解适配器模式——加个“适配器”以便于复用](https://segmentfault.com/a/1190000011856448) + - [适配器模式原理及实例介绍-IBM](https://www.ibm.com/developerworks/cn/java/j-lo-adapter-pattern/index.html) +- **桥接模式:** [设计模式笔记16:桥接模式(Bridge Pattern)](https://blog.csdn.net/yangzl2008/article/details/7670996) +- **组合模式:** [大话设计模式—组合模式](https://blog.csdn.net/lmb55/article/details/51039781) +- **装饰模式:** [java模式—装饰者模式](https://www.cnblogs.com/chenxing818/p/4705919.html)、[Java设计模式-装饰者模式](https://blog.csdn.net/cauchyweierstrass/article/details/48240147) +- **外观模式:** [java设计模式之外观模式(门面模式)](https://www.cnblogs.com/lthIU/p/5860607.html) +- **享元模式:** [享元模式](http://www.jasongj.com/design_pattern/flyweight/) +- **代理模式:** + - [代理模式原理及实例讲解 (IBM出品,很不错)](https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html) + - [轻松学,Java 中的代理模式及动态代理](https://blog.csdn.net/briblue/article/details/73928350) + - [Java代理模式及其应用](https://blog.csdn.net/justloveyou_/article/details/74203025) + + +## 行为型模式 + +### 行为型模式概述 + +- 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。 +- 行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。 +- 通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。 + +**行为型模式分为类行为型模式和对象行为型模式两种:** + +- **类行为型模式:** 类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。 +- **对象行为型模式:** 对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。 + +![行为型模式](https://user-gold-cdn.xitu.io/2018/6/28/164467dd92c6172c?w=453&h=269&f=png&s=63270) + +- **职责链模式:** +- [Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639) +- [责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html) +- **命令模式:** 在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。 +- **解释器模式:** +- **迭代器模式:** +- **中介者模式:** +- **备忘录模式:** +- **观察者模式:** +- **状态模式:** +- **策略模式:** + +策略模式作为设计原则中开闭原则最典型的体现,也是经常使用的。下面这篇博客介绍了策略模式一般的组成部分和概念,并用了一个小demo去说明了策略模式的应用。 + +[java设计模式之策略模式](https://blog.csdn.net/zlj1217/article/details/81230077) + +- **模板方法模式:** +- **访问者模式:** + diff --git "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" "b/Java/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" similarity index 68% rename from "Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" rename to "Java/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" index 6961e8083da..18d276c4e22 100644 --- "a/Java\347\233\270\345\205\263/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" +++ "b/Java/\350\277\231\345\207\240\351\201\223Java\351\233\206\345\220\210\346\241\206\346\236\266\351\235\242\350\257\225\351\242\230\345\207\240\344\271\216\345\277\205\351\227\256.md" @@ -1,25 +1,66 @@ -> 本文是“最最最常见Java面试题总结”系列第三周的文章。 -> 主要内容: -> 1. Arraylist 与 LinkedList 异同 -> 2. ArrayList 与 Vector 区别 -> 3. HashMap的底层实现 -> 4. HashMap 和 Hashtable 的区别 -> 5. HashMap 的长度为什么是2的幂次方 -> 6. HashMap 多线程操作导致死循环问题 -> 7. HashSet 和 HashMap 区别 -> 8. ConcurrentHashMap 和 Hashtable 的区别 -> 9. ConcurrentHashMap线程安全的具体实现方式/底层具体实现 -> 10. 集合框架底层数据结构总结 + + +- [Arraylist 与 LinkedList 异同](#arraylist-与-linkedlist-异同) + - [补充:数据结构基础之双向链表](#补充:数据结构基础之双向链表) +- [ArrayList 与 Vector 区别](#arraylist-与-vector-区别) +- [HashMap的底层实现](#hashmap的底层实现) + - [JDK1.8之前](#jdk18之前) + - [JDK1.8之后](#jdk18之后) +- [HashMap 和 Hashtable 的区别](#hashmap-和-hashtable-的区别) +- [HashMap 的长度为什么是2的幂次方](#hashmap-的长度为什么是2的幂次方) +- [HashMap 多线程操作导致死循环问题](#hashmap-多线程操作导致死循环问题) +- [HashSet 和 HashMap 区别](#hashset-和-hashmap-区别) +- [ConcurrentHashMap 和 Hashtable 的区别](#concurrenthashmap-和-hashtable-的区别) +- [ConcurrentHashMap线程安全的具体实现方式/底层具体实现](#concurrenthashmap线程安全的具体实现方式底层具体实现) + - [JDK1.7(上面有示意图)](#jdk17(上面有示意图)) + - [JDK1.8 (上面有示意图)](#jdk18-(上面有示意图)) +- [集合框架底层数据结构总结](#集合框架底层数据结构总结) + - [Collection](#collection) + - [1. List](#1-list) + - [2. Set](#2-set) + - [Map](#map) + - [推荐阅读:](#推荐阅读:) + + ## Arraylist 与 LinkedList 异同 - **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全; -- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构; +- **2. 底层数据结构:** Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别:); 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html) - **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e) `方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element) `)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。** -- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 +- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index) `方法)。 - **5. 内存空间占用:** ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。 +- **6.补充内容:RandomAccess接口** + +```java +public interface RandomAccess { +} +``` + +查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。 + +在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法 + +```java + public static + int binarySearch(List> list, T key) { + if (list instanceof RandomAccess || list.size() MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); + } + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } +``` + +下面这个方法保证了 HashMap 总是使用2的幂作为哈希表的大小。 + +```java + /** + * Returns a power of two size for the given target capacity. + */ + static final int tableSizeFor(int cap) { + int n = cap - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } +``` + ## HashMap 的长度为什么是2的幂次方 -为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483648,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 +为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648到2147483647,前后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash` ”。(n代表数组长度)。这也就解释了 HashMap 的长度为什么是2的幂次方。 **这个算法应该如何设计呢?** @@ -124,6 +202,7 @@ static int hash(int h) { 这个过程为,先将 A 复制到新的 hash 表中,然后接着复制 B 到链头(A 的前边:B.next=A),本来 B.next=null,到此也就结束了(跟线程二一样的过程),但是,由于线程二扩容的原因,将 B.next=A,所以,这里继续复制A,让 A.next=B,由此,环形链表出现:B.next=A; A.next=B +**注意:jdk1.8已经解决了死循环的问题。**详细信息请阅读[jdk1.8 hashmap多线程put不会造成死循环](https://blog.csdn.net/qq_27007251/article/details/71403647) ## HashSet 和 HashMap 区别 @@ -137,7 +216,7 @@ static int hash(int h) { ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。 - **底层数据结构:** JDK1.7的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的; -- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 +- **实现线程安全的方式(重要):** ① **在JDK1.7的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 **到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)** :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 **两者的对比图:** @@ -158,7 +237,7 @@ Node: 链表节点): 首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。 -**ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成**。 +**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。 Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。 @@ -183,7 +262,8 @@ synchronized只锁定当前链表或红黑二叉树的首节点,这样只要ha #### 1. List - **Arraylist:** Object数组 - **Vector:** Object数组 - - **LinkedList:** 双向循环链表 + - **LinkedList:** 双向链表(JDK1.6之前为循环链表,JDK1.7取消了循环) + 详细可阅读[JDK1.7-LinkedList循环链表优化](https://www.cnblogs.com/xingele0917/p/3696593.html) #### 2. Set - **HashSet(无序,唯一):** 基于 HashMap 实现的,底层采用 HashMap 来保存元素 diff --git "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" deleted file mode 100644 index 643550b3ed4..00000000000 --- "a/Java\347\233\270\345\205\263/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ /dev/null @@ -1,380 +0,0 @@ - - - -- [1. 面向对象和面向过程的区别](#1-面向对象和面向过程的区别) - - [面向过程](#面向过程) - - [面向对象](#面向对象) -- [2. Java 语言有哪些特点?](#2-java-语言有哪些特点?) -- [3. 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别](#3-什么是-jdk?什么是-jre?什么是-jvm?三者之间的联系与区别) -- [4. 什么是字节码?采用字节码的最大好处是什么?](#4-什么是字节码?采用字节码的最大好处是什么?) - - [先看下 java 中的编译器和解释器:](#先看下-java-中的编译器和解释器:) - - [采用字节码的好处:](#采用字节码的好处:) -- [5. Java和C++的区别](#5-java和c的区别) -- [6. 什么是 Java 程序的主类?应用程序和小程序的主类有何不同?](#6-什么是-java-程序的主类?应用程序和小程序的主类有何不同?) -- [7. Java 应用程序与小程序之间有那些差别?](#7-java-应用程序与小程序之间有那些差别?) -- [8. 字符型常量和字符串常量的区别](#8-字符型常量和字符串常量的区别) -- [9. 构造器 Constructor 是否可被 override](#9-构造器-constructor-是否可被-override) -- [10. 重载和重写的区别](#10-重载和重写的区别) -- [11. Java 面向对象编程三大特性:封装、继承、多态](#11-java-面向对象编程三大特性封装、继承、多态) - - [封装](#封装) - - [继承](#继承) - - [多态](#多态) -- [12. String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?](#12-string-和-stringbuffer、stringbuilder-的区别是什么?string-为什么是不可变的?) -- [13. 自动装箱与拆箱](#13-自动装箱与拆箱) -- [14. 在一个静态方法内调用一个非静态成员为什么是非法的?](#14-在一个静态方法内调用一个非静态成员为什么是非法的?) -- [15. 在 Java 中定义一个不做事且没有参数的构造方法的作用](#15-在-java-中定义一个不做事且没有参数的构造方法的作用) -- [16. import java和javax有什么区别](#16-import-java和javax有什么区别) -- [17. 接口和抽象类的区别是什么?](#17-接口和抽象类的区别是什么?) -- [18. 成员变量与局部变量的区别有那些?](#18-成员变量与局部变量的区别有那些?) -- [19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?](#19-创建一个对象用什么运算符?对象实体与对象引用有何不同?) -- [20. 什么是方法的返回值?返回值在类的方法里的作用是什么?](#20-什么是方法的返回值?返回值在类的方法里的作用是什么?) -- [21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么?](#21-一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么?) -- [22. 构造方法有哪些特性?](#22-构造方法有哪些特性?) -- [23. 静态方法和实例方法有何不同?](#23-静态方法和实例方法有何不同?) -- [24. 对象的相等与指向他们的引用相等,两者有什么不同?](#24-对象的相等与指向他们的引用相等,两者有什么不同?) -- [25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?](#25-在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?) -- [26. == 与 equals\(重要\)](#26--与-equals重要) -- [27. hashCode 与 equals(重要)](#27-hashcode-与-equals(重要)) - - [hashCode()介绍](#hashcode()介绍) - - [为什么要有 hashCode](#为什么要有-hashcode) - - [hashCode()与equals()的相关规定](#hashcode()与equals()的相关规定) -- [28. Java中的值传递和引用传递](#28-java中的值传递和引用传递) -- [29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么?](#29-简述线程,程序、进程的基本概念。以及他们之间关系是什么?) -- [30. 线程有哪些基本状态?这些状态是如何定义的?](#30-线程有哪些基本状态?这些状态是如何定义的) -- [31 关于 final 关键字的一些总结](#31-关于-final-关键字的一些总结) -- [Java基础学习书籍推荐](#java基础学习书籍推荐) - - - - -## 1. 面向对象和面向过程的区别 - -### 面向过程 - -**优点:** 性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。 - -**缺点:** 没有面向对象易维护、易复用、易扩展 - -### 面向对象 - -**优点:** 易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护 - -**缺点:** 性能比面向过程低 - -## 2. Java 语言有哪些特点? - -1. 简单易学; -2. 面向对象(封装,继承,多态); -3. 平台无关性( Java 虚拟机实现平台无关性); -4. 可靠性; -5. 安全性; -6. 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程程序设计,而 Java 语言却提供了多线程支持); -7. 支持网络编程并且很方便( Java 语言诞生本身就是为简化网络编程设计的,因此 Java 语言不仅支持网络编程而且很方便); -8. 编译与解释并存; - -## 3. 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别 - -这几个是Java中很基本很基本的东西,但是我相信一定还有很多人搞不清楚!为什么呢?因为我们大多数时候在使用现成的编译工具以及环境的时候,并没有去考虑这些东西。 - -**JDK:** 顾名思义它是给开发者提供的开发工具箱,是给程序开发者用的。它除了包括完整的JRE(Java Runtime Environment),Java运行环境,还包含了其他供开发者使用的工具包。 - -**JRE:** 普通用户而只需要安装 JRE(Java Runtime Environment)来运行 Java 程序。而程序开发者必须安装JDK来编译、调试程序。 - -**JVM:** 当我们运行一个程序时,JVM 负责将字节码转换为特定机器代码,JVM 提供了内存管理/垃圾回收和安全机制等。这种独立于硬件和操作系统,正是 java 程序可以一次编写多处执行的原因。 - -**区别与联系:** - - 1. JDK 用于开发,JRE 用于运行java程序 ; - 2. JDK 和 JRE 中都包含 JVM ; - 3. JVM 是 java 编程语言的核心并且具有平台独立性。 - -## 4. 什么是字节码?采用字节码的最大好处是什么? - -### 先看下 java 中的编译器和解释器:    - -Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。 - -编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。 - -每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。 - - Java 源代码---->编译器---->jvm 可执行的 Java 字节码(即虚拟指令)---->jvm---->jvm 中解释器----->机器可执行的二进制机器码---->程序运行。 - -### 采用字节码的好处:  - -Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。 - -## 5. Java和C++的区别 - -我知道很多人没学过 C++,但是面试官就是没事喜欢拿咱们 Java 和 C++ 比呀!没办法!!!就算没学过C++,也要记下来! - -- 都是面向对象的语言,都支持封装、继承和多态 -- Java 不提供指针来直接访问内存,程序内存更加安全 -- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。 -- Java 有自动内存管理机制,不需要程序员手动释放无用内存 - - -## 6. 什么是 Java 程序的主类?应用程序和小程序的主类有何不同? - -一个程序中可以有多个类,但只能有一个类是主类。在 Java 应用程序中,这个主类是指包含 main()方法的类。而在 Java 小程序中,这个主类是一个继承自系统类 JApplet 或 Applet 的子类。应用程序的主类不一定要求是 public 类,但小程序的主类要求必须是 public 类。主类是 Java 程序执行的入口点。 - -## 7. Java 应用程序与小程序之间有那些差别? - -简单说应用程序是从主线程启动(也就是 main() 方法)。applet 小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟 flash 的小游戏类似。 - -## 8. 字符型常量和字符串常量的区别 - -1. 形式上: 字符常量是单引号引起的一个字符 字符串常量是双引号引起的若干个字符 -2. 含义上: 字符常量相当于一个整形值( ASCII 值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置) -3. 占内存大小 字符常量只占一个字节 字符串常量占若干个字节(至少一个字符结束标志) - -## 9. 构造器 Constructor 是否可被 override - -在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor 也就不能被 override,但是可以 overload,所以你可以看到一个类中有多个构造函数的情况。 - -## 10. 重载和重写的区别 - -**重载:** 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。    - -**重写:** 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。 - -## 11. Java 面向对象编程三大特性:封装、继承、多态 - -### 封装 - -封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。 - - -### 继承 -继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。 - -**关于继承如下 3 点请记住:** - -1. 子类拥有父类非 private 的属性和方法。 -2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。 -3. 子类可以用自己的方式实现父类的方法。(以后介绍)。 - -### 多态 - -所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。 - -在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。 - -## 12. String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的? - -**可变性** -  - -String 类中使用字符数组保存字符串,private final char value[],所以 String 对象是不可变的。StringBuilder 与 StringBuffer 都继承自AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。 -   - -**线程安全性** - -String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。 -   - -**性能** - -每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StirngBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。 - -**对于三者使用的总结:** -如果要操作少量的数据用 = String -单线程操作字符串缓冲区 下操作大量数据 = StringBuilder -多线程操作字符串缓冲区 下操作大量数据 = StringBuffer - -## 13. 自动装箱与拆箱 -**装箱**:将基本类型用它们对应的引用类型包装起来; - -**拆箱**:将包装类型转换为基本数据类型; - -## 14. 在一个静态方法内调用一个非静态成员为什么是非法的? - -由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。 - -## 15. 在 Java 中定义一个不做事且没有参数的构造方法的作用 - Java 程序在执行子类的构造方法之前,如果没有用 super() 来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。因此,如果父类中只定义了有参数的构造方法,而在子类的构造方法中又没有用 super() 来调用父类中特定的构造方法,则编译时将发生错误,因为 Java 程序在父类中找不到没有参数的构造方法可供执行。解决办法是在父类里加上一个不做事且没有参数的构造方法。 -  -## 16. import java和javax有什么区别 - -刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java 包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分。 - -所以,实际上java和javax没有区别。这都是一个名字。 - -## 17. 接口和抽象类的区别是什么? - -1. 接口的方法默认是 public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法 -2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定 -3. 一个类可以实现多个接口,但最多只能实现一个抽象类 -4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定 -5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。 - -## 18. 成员变量与局部变量的区别有那些? - -1. 从语法形式上,看成员变量是属于类的,而局部变量是在方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰; -2. 从变量在内存中的存储方式来看,成员变量是对象的一部分,而对象存在于堆内存,局部变量存在于栈内存 -3. 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。 -4. 成员变量如果没有被赋初值,则会自动以类型的默认值而赋值(一种情况例外被 final 修饰但没有被 static 修饰的成员变量必须显示地赋值);而局部变量则不会自动赋值。 - -## 19. 创建一个对象用什么运算符?对象实体与对象引用有何不同? - -new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向0个或1个对象(一根绳子可以不系气球,也可以系一个气球);一个对象可以有n个引用指向它(可以用n条绳子系住一个气球)。 - -## 20. 什么是方法的返回值?返回值在类的方法里的作用是什么? - -方法的返回值是指我们获取到的某个方法体中的代码执行后产生的结果!(前提是该方法可能产生结果)。返回值的作用:接收出结果,使得它可以用于其他的操作! - -## 21. 一个类的构造方法的作用是什么?若一个类没有声明构造方法,该程序能正确执行吗?为什么? - -主要作用是完成对类对象的初始化工作。可以执行。因为一个类即使没有声明构造方法也会有默认的不带参数的构造方法。 - -## 22. 构造方法有哪些特性? - -1. 名字与类名相同; -2. 没有返回值,但不能用void声明构造函数; -3. 生成类的对象时自动执行,无需调用。 - -## 23. 静态方法和实例方法有何不同? - -1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。 - -2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制. - -## 24. 对象的相等与指向他们的引用相等,两者有什么不同? - -对象的相等 比的是内存中存放的内容是否相等而引用相等 比较的是他们指向的内存地址是否相等。 - -## 25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是? - -帮助子类做初始化工作。 - -## 26. == 与 equals(重要) - -**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址) - -**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况: -- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。 -- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。 - - -**举个例子:** - -```java -public class test1 { - public static void main(String[] args) { - String a = new String("ab"); // a 为一个引用 - String b = new String("ab"); // b为另一个引用,对象的内容一样 - String aa = "ab"; // 放在常量池中 - String bb = "ab"; // 从常量池中查找 - if (aa == bb) // true - System.out.println("aa==bb"); - if (a == b) // false,非同一对象 - System.out.println("a==b"); - if (a.equals(b)) // true - System.out.println("aEQb"); - if (42 == 42.0) { // true - System.out.println("true"); - } - } -} -``` - -**说明:** -- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。 -- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。 - - - -## 27. hashCode 与 equals(重要) - -面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” - -### hashCode()介绍 -hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。 - -散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象) - -### 为什么要有 hashCode - - -**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:** - -当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。 - - - -### hashCode()与equals()的相关规定 - -1. 如果两个对象相等,则hashcode一定也是相同的 -2. 两个对象相等,对两个对象分别调用equals方法都返回true -3. 两个对象有相同的hashcode值,它们也不一定是相等的 -4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖** -5. hashCode() 的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据) - - -## 28. Java中的值传递和引用传递 - -**值传递**是指对象被值传递,意味着传递了对象的一个副本,即使副本被改变,也不会影响源对象。(因为值传递的时候,实际上是将实参的值复制一份给形参。) - -**引用传递**是指对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象的改变会反映到所有的对象上。(因为引用传递的时候,实际上是将实参的地址值复制一份给形参。) - - -## 29. 简述线程,程序、进程的基本概念。以及他们之间关系是什么? - -**线程**与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。 - -**程序**是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。 - -**进程**是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 -线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。 - -## 30. 线程有哪些基本状态?这些状态是如何定义的? - -1. **新建(new)**:新创建了一个线程对象。 -2. **可运行(runnable)**:线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取cpu的使用权。 -3. **运行(running)**:可运行状态(runnable)的线程获得了cpu时间片(timeslice),执行程序代码。 -4. **阻塞(block)**:阻塞状态是指线程因为某种原因放弃了cpu使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有 机会再次获得cpu timeslice转到运行(running)状态。阻塞的情况分三种: -(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放 入等待队列(waitting queue)中。 -(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。 -(三). 其他阻塞: 运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。 -5. **死亡(dead)**:线程run()、main()方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。 - -![](https://user-gold-cdn.xitu.io/2018/8/9/1651f19d7c4e93a3?w=876&h=492&f=png&s=128092) - -备注: 可以用早起坐地铁来比喻这个过程: - -还没起床:sleeping - -起床收拾好了,随时可以坐地铁出发:Runnable - -等地铁来:Waiting - -地铁来了,但要排队上地铁:I/O阻塞 - -上了地铁,发现暂时没座位:synchronized阻塞 - -地铁上找到座位:Running - -到达目的地:Dead - -## 31 关于 final 关键字的一些总结 - -final关键字主要用在三个地方:变量、方法、类。 - -1. ①对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。 -2. ②当用final修饰一个类时,表明这个类不能被继承。final类中的所有成员方法都会被隐式地指定为final方法。 -3. ③使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为fianl。 - -# Java基础学习书籍推荐 - -**《Head First Java.第二版》:** -可以说是我的 Java 启蒙书籍了,特别适合新手读当然也适合我们用来温故Java知识点。 - -**《Java核心技术卷1+卷2》:** -很棒的两本书,建议有点 Java 基础之后再读,介绍的还是比较深入的,非常推荐。 - -**《Java编程思想(第4版)》:** -这本书要常读,初学者可以快速概览,中等程序员可以深入看看 Java,老鸟还可以用之回顾 Java 的体系。这本书之所以厉害,因为它在无形中整合了设计模式,这本书之所以难读,也恰恰在于他对设计模式的整合是无形的。 - - - - diff --git "a/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" "b/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" deleted file mode 100644 index 238e7c8b5df..00000000000 --- "a/Java\347\233\270\345\205\263/Java\350\231\232\346\213\237\346\234\272\357\274\210jvm\357\274\211.md" +++ /dev/null @@ -1,67 +0,0 @@ -Java面试通关手册(Java学习指南)github地址(欢迎star和pull):[https://github.com/Snailclimb/Java_Guide](https://github.com/Snailclimb/Java_Guide) - - - -下面是按jvm虚拟机知识点分章节总结的一些jvm学习与面试相关的一些东西。一般作为Java程序员在面试的时候一般会问的大多就是**Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理**这些问题了。这些内容参考周的《深入理解Java虚拟机》中第二章和第三章就足够了对应下面的[深入理解虚拟机之Java内存区域:](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd)和[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd)这两篇文章。 - - -> ### 常见面试题 - -[深入理解虚拟机之Java内存区域:](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483910%26idx%3D1%26sn%3D246f39051a85fc312577499691fba89f%26chksm%3Dfd985467caefdd71f9a7c275952be34484b14f9e092723c19bd4ef557c324169ed084f868bdb%23rd) - -1. 介绍下Java内存区域(运行时数据区)。 - -2. 对象的访问定位的两种方式。 - - -[深入理解虚拟机之垃圾回收](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483914%26idx%3D1%26sn%3D9aa157d4a1570962c39783cdeec7e539%26chksm%3Dfd98546bcaefdd7d9f61cd356e5584e56b64e234c3a403ed93cb6d4dde07a505e3000fd0c427%23rd) - -1. 如何判断对象是否死亡(两种方法)。 - -2. 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 - -3. 垃圾收集有哪些算法,各自的特点? - -4. HotSpot为什么要分为新生代和老年代? - -5. 常见的垃圾回收器有那些? - -6. 介绍一下CMS,G1收集器。 - -7. Minor Gc和Full GC 有什么不同呢? - - - -[虚拟机性能监控和故障处理工具](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483922%26idx%3D1%26sn%3D0695ff4c2700ccebb8fbc39011866bd8%26chksm%3Dfd985473caefdd6583eb42dbbc7f01918dc6827c808292bb74a5b6333e3d526c097c9351e694%23rd) - -1. JVM调优的常见命令行工具有哪些? - -[深入理解虚拟机之类文件结构](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzU4NDQ4MzU5OA%3D%3D%26mid%3D2247483926%26idx%3D1%26sn%3D224413da998f7e024f7b8d87397934d9%26chksm%3Dfd985477caefdd61a2fe1a3f0be29e057082252e579332f5b6d9072a150b838cefe2c47b6e5a%23rd) - -1. 简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?) - -[深入理解虚拟机之虚拟机类加载机制](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247483934&idx=1&sn=f247f9bee4e240f5e7fac25659da3bff&chksm=fd98547fcaefdd6996e1a7046e03f29df9308bdf82ceeffd111112766ffd3187892700f64b40#rd) - -1. 简单说说类加载过程,里面执行了哪些操作? - -2. 对类加载器有了解吗? - -3. 什么是双亲委派模型? - -4. 双亲委派模型的工作过程以及使用它的好处。 - - - - - -> ### 推荐阅读 - -[深入理解虚拟机之虚拟机字节码执行引擎](https://juejin.im/post/5aebcb076fb9a07a9a10b5f3) - -[《深入理解 Java 内存模型》读书笔记](http://www.54tianzhisheng.cn/2018/02/28/Java-Memory-Model/) (非常不错的文章) - -[全面理解Java内存模型(JMM)及volatile关键字 ](https://blog.csdn.net/javazejian/article/details/72772461) - -**欢迎关注我的微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各种Java学习资源):** - -![微信公众号](https://user-gold-cdn.xitu.io/2018/3/19/1623c870135a3609?w=215&h=215&f=jpeg&s=29172) diff --git "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index 32a78ea6780..00000000000 --- "a/Java\347\233\270\345\205\263/\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,116 +0,0 @@ -下面是自己学习设计模式的时候做的总结,有些是自己的原创文章,有些是网上写的比较好的文章,保存下来细细消化吧! - -## 创建型模式: - -> ### 创建型模式概述: - -- 创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系统的设计更加符合单一职责原则。 -- 创建型模式在创建什么(What),由谁创建(Who),何时创建(When)等方面都为软件设计者提供了尽可能大的灵活性。创建型模式隐藏了类的实例的创建细节,通过隐藏对象如何被创建和组合在一起达到使整个系统独立的目的。 - -![创建型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640641afcb7559b?w=491&h=241&f=png&s=51443) - -> ### 创建型模式系列文章推荐: - -- **单例模式:** - -[深入理解单例模式——只有一个实例](https://blog.csdn.net/qq_34337272/article/details/80455972) - -- **工厂模式:** - -[深入理解工厂模式——由对象工厂生成对象](https://blog.csdn.net/qq_34337272/article/details/80472071) - -- **建造者模式:** - -[深入理解建造者模式 ——组装复杂的实例](http://blog.csdn.net/qq_34337272/article/details/80540059) - -- **原型模式:** - -[深入理解原型模式 ——通过复制生成实例](https://blog.csdn.net/qq_34337272/article/details/80706444) - - -## 结构型模式: - -> ### 结构型模式概述: - -- **结构型模式(Structural Pattern):** 描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构 -![结构型模式(Structural Pattern)](https://user-gold-cdn.xitu.io/2018/6/16/164064d6b3c205e3?w=719&h=233&f=png&s=270293) -- **结构型模式可以分为类结构型模式和对象结构型模式:** - - 类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。 - - 对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。 - -![结构型模式](https://user-gold-cdn.xitu.io/2018/6/16/1640655459d766d2?w=378&h=266&f=png&s=59652) - -> ### 结构型模式系列文章推荐: - -- **适配器模式:** - -[深入理解适配器模式——加个“适配器”以便于复用](https://segmentfault.com/a/1190000011856448) - -[适配器模式原理及实例介绍-IBM](https://www.ibm.com/developerworks/cn/java/j-lo-adapter-pattern/index.html) - -- **桥接模式:** - -[设计模式笔记16:桥接模式(Bridge Pattern)](https://blog.csdn.net/yangzl2008/article/details/7670996) - -- **组合模式:** - -[大话设计模式—组合模式](https://blog.csdn.net/lmb55/article/details/51039781) - -- **装饰模式:** - -[java模式—装饰者模式](https://www.cnblogs.com/chenxing818/p/4705919.html) - -[Java设计模式-装饰者模式](https://blog.csdn.net/cauchyweierstrass/article/details/48240147) - -- **外观模式:** - -[java设计模式之外观模式(门面模式)](https://www.cnblogs.com/lthIU/p/5860607.html) - -- **享元模式:** - -[享元模式](http://www.jasongj.com/design_pattern/flyweight/) - -- **代理模式:** - -[代理模式原理及实例讲解 (IBM出品,很不错)](https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html) - -[轻松学,Java 中的代理模式及动态代理](https://blog.csdn.net/briblue/article/details/73928350) - -[Java代理模式及其应用](https://blog.csdn.net/justloveyou_/article/details/74203025) - - -## 行为型模式 - -> ### 行为型模式概述: - -- 行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。 -- 行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。 -- 通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。 - -**行为型模式分为类行为型模式和对象行为型模式两种:** - -- **类行为型模式:** 类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。 -- **对象行为型模式:** 对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。 - -![行为型模式](https://user-gold-cdn.xitu.io/2018/6/28/164467dd92c6172c?w=453&h=269&f=png&s=63270) - -- **职责链模式:** - -[Java设计模式之责任链模式、职责链模式](https://blog.csdn.net/jason0539/article/details/45091639) - -[责任链模式实现的三种方式](https://www.cnblogs.com/lizo/p/7503862.html) - -- **命令模式:** - - - -- **解释器模式:** -- **迭代器模式:** -- **中介者模式:** -- **备忘录模式:** -- **观察者模式:** -- **状态模式:** -- **策略模式:** -- **模板方法模式:** -- **访问者模式:** - diff --git a/README.md b/README.md index 9d5d8534096..26366a59f70 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,297 @@ -> **Java学习指南:** 一份涵盖大部分Java程序员所需要掌握的核心知识,正在一步一步慢慢完善,期待您的参与。 - -| Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | -| :--------: | :----------: | :-----------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :----:| -| [Java](#coffee-Java) | [数据结构与算法](#open_file_folder-数据结构与算法)|[计算机网络与数据通信](#computer-计算机网络与数据通信) | [操作系统](#iphone-操作系统)| [主流框架](#pencil2-主流框架)| [数据存储](#floppy_disk-数据存储)|[架构](#punch-架构)| [面试必备](#musical_note-面试必备)| [其他](#art-其他)| [说明](#envelope-该开源文档一些说明)| - -## :coffee: Java -- ### Java/J2EE 基础 - - [Java 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java基础知识.md) - - [J2EE 基础知识回顾](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/J2EE基础知识.md) -- ### Java 集合框架 - - [这几道Java集合框架面试题几乎必问](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) - - [Java 集合框架常见面试题总结](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/Java集合框架常见面试题总结.md) - - [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) - - [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) - - [HashMap(JDK1.8)源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/HashMap.md) - -- ### Java 多线程 - - [多线程系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/多线程系列.md) - - [值得立马保存的 synchronized 关键字总结](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/synchronized.md) - -- ### Java IO 与 NIO - - [Java IO 与 NIO系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java%20IO与NIO.md) - -- ### Java虚拟机(jvm) - - [可能是把Java内存区域讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/可能是把Java内存区域讲的最清楚的一篇文章.md) - - [搞定JVM垃圾回收就是这么简单](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/搞定JVM垃圾回收就是这么简单.md) - - [Java虚拟机(jvm)学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/Java虚拟机(jvm).md) -- ### 设计模式 - - [设计模式系列文章](https://github.com/Snailclimb/Java_Guide/blob/master/Java相关/设计模式.md) - -## :open_file_folder: 数据结构与算法 - -- ### 数据结构 - - [数据结构知识学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/数据结构.md) - - -- ### 算法 - - [算法学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/算法.md) - - - [常见安全算法(MD5、SHA1、Base64等等)总结](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) - - [搞定 BAT 面试——几道常见的子符串算法题 ](https://github.com/Snailclimb/Java_Guide/blob/master/数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) - -## :computer: 计算机网络与数据通信 -- ### 网络相关 - - [计算机网络常见面试题](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/计算机网络.md) - - [计算机网络基础知识总结](https://github.com/Snailclimb/Java_Guide/blob/master/计算机网络与数据通信/干货:计算机网络知识总结.md) +

Java 学习/面试指南

+ +

+ +

+ +

+ 微信群 + 码云地址 + 公众号 + 公众号 -- ### 数据通信(RESTful、RPC、消息队列) - - [数据通信(RESTful、RPC、消息队列)相关知识点总结](https://github.com/Snailclimb/Java-Guide/blob/master/计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md) +

+ +

Special Sponsors

+ +

+ + + +

+ + +1. **码云地址:** 如果访问速度比较慢的话,可以考虑访问本仓库的码云地址: ,Gitee 和 都是同步更新的。 +1. **关于贡献者:** 对本仓库提过有价值的 issue 或 pr 的小伙伴将出现在 [Contributor](#Contributor) 这里。 +1. **欢迎投稿:** 由于我个人能力有限,很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。**对于不错的原创文章我根据你的选择给予现金(50-300)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。 + +## 目录 + +* [Java](#java) + * [基础](#基础) + * [集合框架](#集合框架) + * [多线程](#多线程) + * [BIO,NIO,AIO](#bionioaio) + * [JVM](#jvm) + * [Java8 New Features](#java8-new-features) + * [设计模式](#设计模式) +* [数据结构与算法](#数据结构与算法) + * [数据结构](#数据结构) + * [算法](#算法) +* [计算机网络与数据通信](#计算机网络与数据通信) + * [网络相关](#网络相关) + * [数据通信\(RESTful,RPC,消息队列\)总结](#数据通信restfulrpc消息队列总结) +* [操作系统](#操作系统) + * [Linux相关](#linux相关) +* [主流框架](#主流框架) + * [Spring](#spring) + * [ZooKeeper](#zookeeper) +* [数据存储](#数据存储) + * [MySQL](#mysql) + * [Redis](#redis) +* [架构](#架构) +* [面试必备\(Essential content for the interview\)](#面试必备essential-content-for-the-interview) + * [备战面试\(Preparing for an interview\)](#备战面试preparing-for-an-interview) + * [BATJ真实面经\(BATJ real interview experience\)](#batj真实面经batj-real-interview-experience) + * [最常见的Java面试题总结\(Summary of the most common Java interview questions\)](#最常见的java面试题总结summary-of-the-most-common-java-interview-questions) +* [开发常用工具](#开发常用工具) + * [Git](#Git) +* [闲谈](#闲谈) +* [说明](#说明) + +## ToDoList(待办清单) + +* [x] [Java 8 新特性总结](./Java/What's%20New%20in%20JDK8/Java8Tutorial.md) +* [ ] Java 8 新特性详解 +* [ ] Java 多线程类别知识重构 +* [x] [BIO,NIO,AIO 总结 ](./Java/BIO%2CNIO%2CAIO%20summary.md) +* [ ] Netty 总结 +* [ ] 数据结构总结重构 +## Java + +### 基础 + +* [Java 基础知识回顾](./Java/Java基础知识.md) +* [J2EE 基础知识回顾](./Java/J2EE基础知识.md) +* [Collections 工具类和 Arrays 工具类常见方法](./Java/Basis/Arrays%2CCollectionsCommonMethods.md) +* [Java常见关键字总结:static、final、this、super](./Java/Basis/final、static、this、super.md) + +### 集合框架 + +* **常见问题总结:** + * [这几道Java集合框架面试题几乎必问](./Java/这几道Java集合框架面试题几乎必问.md) + * [Java 集合框架常见面试题总结](./Java/Java集合框架常见面试题总结.md) +* **源码分析:** + * [ArrayList 源码学习](./Java/ArrayList.md) + * [【面试必备】透过源码角度一步一步带你分析 ArrayList 扩容机制](./Java/ArrayList-Grow.md) + * [LinkedList 源码学习](./Java/LinkedList.md) + * [HashMap(JDK1.8)源码学习](./Java/HashMap.md) + +### 多线程 + +* [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](./Java/synchronized.md) +* [并发编程面试必备:乐观锁与悲观锁](./EssentialContentForInterview/面试必备之乐观锁与悲观锁.md) +* [并发编程面试必备:JUC 中的 Atomic 原子类总结](./Java/Multithread/Atomic.md) +* [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](./Java/Multithread/AQS.md) +* [BATJ都爱问的多线程面试题](./Java/Multithread/BATJ都爱问的多线程面试题.md) +* [并发容器总结](./Java/Multithread/并发容器总结.md) + +### JVM + +* [可能是把Java内存区域讲的最清楚的一篇文章](./Java/可能是把Java内存区域讲的最清楚的一篇文章.md) +* [搞定JVM垃圾回收就是这么简单](./Java/搞定JVM垃圾回收就是这么简单.md) +* [《深入理解Java虚拟机》第2版学习笔记](./Java/Java虚拟机(jvm).md) + +### BIO,NIO,AIO + +* [BIO,NIO,AIO 总结 ](./Java/BIO%2CNIO%2CAIO%20summary.md) +* [Java IO 与 NIO系列文章](./Java/Java%20IO与NIO.md) + +### Java8 New Features + +* [Java 8 新特性总结](./Java/What's%20New%20in%20JDK8/Java8Tutorial.md) + +### 设计模式 + +* [设计模式系列文章](./Java/设计模式.md) + +## 数据结构与算法 + +### 数据结构 + +* [数据结构知识学习与面试](./数据结构与算法/数据结构.md) + +### 算法 + +* [算法学习与面试](./数据结构与算法/算法.md) +* [常见安全算法(MD5、SHA1、Base64等等)总结](./数据结构与算法/常见安全算法(MD5、SHA1、Base64等等)总结.md) +* [算法总结——几道常见的子符串算法题 ](./数据结构与算法/搞定BAT面试——几道常见的子符串算法题.md) +* [算法总结——几道常见的链表算法题 ](./数据结构与算法/Leetcode-LinkList1.md) + +## 计算机网络与数据通信 + +### 网络相关 + +* [计算机网络常见面试题](./计算机网络与数据通信/计算机网络.md) +* [计算机网络基础知识总结](./计算机网络与数据通信/干货:计算机网络知识总结.md) +* [HTTPS中的TLS](./计算机网络与数据通信/HTTPS中的TLS.md) + +### 数据通信(RESTful,RPC,消息队列)总结 + +* [数据通信(RESTful、RPC、消息队列)相关知识点总结](./计算机网络与数据通信/数据通信(RESTful、RPC、消息队列).md) +* [Dubbo 总结:关于 Dubbo 的重要知识点](./计算机网络与数据通信/dubbo.md) +* [消息队列总结:新手也能看懂,消息队列其实很简单](./计算机网络与数据通信/message-queue.md) +* [一文搞懂 RabbitMQ 的重要概念以及安装](./计算机网络与数据通信/rabbitmq.md) + +## 操作系统 + +### Linux相关 + +* [后端程序员必备的 Linux 基础知识](./操作系统/后端程序员必备的Linux基础知识.md) +* [Shell 编程入门](./操作系统/Shell.md) + +## 主流框架 + +### Spring + +* [Spring 学习与面试](./主流框架/Spring学习与面试.md) +* [Spring中bean的作用域与生命周期](./主流框架/SpringBean.md) +* [SpringMVC 工作原理详解](./主流框架/SpringMVC%20%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3.md) + +### ZooKeeper + +* [可能是把 ZooKeeper 概念讲的最清楚的一篇文章](./主流框架/ZooKeeper.md) +* [ZooKeeper 数据模型和常见命令了解一下,速度收藏!](./主流框架/ZooKeeper数据模型和常见命令.md) + +## 数据存储 + +### MySQL + +* [MySQL 学习与面试](./数据存储/MySQL.md) +* [【思维导图-索引篇】搞定数据库索引就是这么简单](./数据存储/MySQL%20Index.md) +* [一千行MySQL学习笔记](./数据存储/一千行MySQL命令.md) + +### Redis + +* [Redis 总结](./数据存储/Redis/Redis.md) +* [Redlock分布式锁](./数据存储/Redis/Redlock分布式锁.md) +* [如何做可靠的分布式锁,Redlock真的可行么](./数据存储/Redis/如何做可靠的分布式锁,Redlock真的可行么.md) + +## 架构 + +* [一文读懂分布式应该学什么](./架构/分布式.md) +* [8 张图读懂大型网站技术架构](./架构/8%20张图读懂大型网站技术架构.md) +* [【面试精选】关于大型网站系统架构你不得不懂的10个问题](./架构/【面试精选】关于大型网站系统架构你不得不懂的10个问题.md) + +## 面试必备(Essential content for the interview) + +### 备战面试(Preparing for an interview) + +* [【备战面试1】程序员的简历就该这样写](./EssentialContentForInterview/PreparingForInterview/程序员的简历之道.md) +* [【备战面试2】初出茅庐的程序员该如何准备面试?](./EssentialContentForInterview/PreparingForInterview/interviewPrepare.md) +* [【备战面试3】7个大部分程序员在面试前很关心的问题](./EssentialContentForInterview/PreparingForInterview/JavaProgrammerNeedKnow.md) +* [【备战面试4】Java程序员必备书单](./EssentialContentForInterview/PreparingForInterview/books.md) +* [【备战面试5】Github上开源的Java面试/学习相关的仓库推荐](./EssentialContentForInterview/PreparingForInterview/JavaInterviewLibrary.md) +* [【备战面试6】如果面试官问你“你有什么问题问我吗?”时,你该如何回答](./EssentialContentForInterview/PreparingForInterview/如果面试官问你“你有什么问题问我吗?”时,你该如何回答.md) +* [【备战面试7】美团面试常见问题总结(附详解答案)](./EssentialContentForInterview/PreparingForInterview/美团面试常见问题总结.md) + +### BATJ真实面经(BATJ real interview experience) + +* [5面阿里,终获offer(2018年秋招)](./EssentialContentForInterview/BATJrealInterviewExperience/5面阿里,终获offer.md) + +### 最常见的Java面试题总结(Summary of the most common Java interview questions) + +* [第一周(2018-8-7)](./EssentialContentForInterview/MostCommonJavaInterviewQuestions/第一周(2018-8-7).md) (为什么 Java 中只有值传递、==与equals、 hashCode与equals) +* [第二周(2018-8-13)](./EssentialContentForInterview/MostCommonJavaInterviewQuestions/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) +* [第三周(2018-08-22)](./Java/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) +* [第四周(2018-8-30).md](./EssentialContentForInterview/MostCommonJavaInterviewQuestions/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) + +## 开发常用工具 + +### Git + +* [Git入门看这一篇就够了](./DevelopCommonTools/GitIntroduce.md) + +## 闲谈 + +* [选择技术方向都要考虑哪些因素](./闲谈/选择技术方向都要考虑哪些因素.md) +* [结束了我短暂的秋招,说点自己的感受](./闲谈/2018%20%E7%A7%8B%E6%8B%9B.md) +* [【2018总结】即使平凡,也要热爱自己的生活](./闲谈/2018%20summary.md) +* [Java项目 Github Trending 月榜](./闲谈/JavaGithubTrending/JavaGithubTrending.md) -## :iphone: 操作系统 - -- ### Linux相关 - - [后端程序员必备的 Linux 基础知识](https://github.com/Snailclimb/Java-Guide/blob/master/操作系统/后端程序员必备的Linux基础知识.md) - -## :pencil2: 主流框架 - -- ### Spring相关 - - [Spring 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/Spring学习与面试.md) - - [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://github.com/Snailclimb/Java_Guide/blob/master/主流框架/ZooKeeper.md) -## :floppy_disk: 数据存储 -- ### MySQL - - [MySQL 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/MySQL.md) -- ### Redis - - [Redis 学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/数据存储/Redis.md) - -## :punch: 架构 -- ### 分布式相关 - - [分布式学习与面试](https://github.com/Snailclimb/Java_Guide/blob/master/架构/分布式.md) - -## :musical_note: 面试必备 - -- ### 面试必备知识点 - - [面试必备之乐观锁与悲观锁](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/面试必备之乐观锁与悲观锁.md) -- ### 最最最常见的Java面试题总结 - 这里会分享一些出现频率极其极其高的面试题,初定周更一篇,什么时候更完什么时候停止。 - - - [第一周(2018-8-7)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第一周(2018-8-7).md) (值传递和引用传递、==与equals、 hashCode与equals) - - [第二周(2018-8-13)](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第二周(2018-8-13).md)(String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的?、什么是反射机制?反射机制的应用场景有哪些?......) - - [第三周(2018-08-22)](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/这几道Java集合框架面试题几乎必问.md) (Arraylist 与 LinkedList 异同、ArrayList 与 Vector 区别、HashMap的底层实现、HashMap 和 Hashtable 的区别、HashMap 的长度为什么是2的幂次方、HashSet 和 HashMap 区别、ConcurrentHashMap 和 Hashtable 的区别、ConcurrentHashMap线程安全的具体实现方式/底层具体实现、集合框架底层数据结构总结) - - [第四周(2018-8-30).md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/最最最常见的Java面试题总结/第四周(2018-8-30).md) (主要内容是几道面试常问的多线程基础题。) -- ### 程序员如何写简历 - - [程序员的简历之道](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/程序员的简历之道.md) - - [手把手教你用Markdown写一份高质量的简历](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/手把手教你用Markdown写一份高质量的简历.md) - -## :art: 其他 - -- ### 个人书单推荐 - - [个人阅读书籍清单](https://github.com/Snailclimb/Java-Guide/blob/master/其他/个人阅读书籍清单.md) - -- ### 技术方向选择 - - [选择技术方向都要考虑哪些因素](https://github.com/Snailclimb/Java-Guide/blob/master/其他/选择技术方向都要考虑哪些因素.md) - *** -> # :envelope: 该开源文档一些说明 +## 说明 + +### 介绍 + +* **对于 Java 初学者来说:** 本文档倾向于给你提供一个比较详细的学习路径,让你对于Java整体的知识体系有一个初步认识。另外,本文的一些文章 +也是你学习和复习 Java 知识不错的实践; +* **对于非 Java 初学者来说:** 本文档更适合回顾知识,准备面试,搞清面试应该把重心放在那些问题上。要搞清楚这个道理:提前知道那些面试常见,不是为了背下来应付面试,而是为了让你可以更有针对的学习重点。 + +本文档 Markdown 格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 + +### 关于转载 + +如果你需要转载本仓库的一些文章到自己的博客的话,记得注明原文地址就可以了。 + +### 如何对该开源文档进行贡献 + +1. 笔记内容大多是手敲,所以难免会有笔误,你可以帮我找错别字。 +2. 很多知识点我可能没有涉及到,所以你可以对其他知识点进行补充。(**对于不错的原创文章我根据你的选择给予现金奖励、付费专栏或者书籍进行奖励!快提 pr 给我投稿吧!**) +3. 现有的知识点难免存在不完善或者错误,所以你可以对已有知识点的修改/补充。 + +### 为什么要做这个开源文档? -## 介绍 -该文档主要是笔主在学习Java的过程中的一些学习笔记,但是为了能够涉及到大部分后端学习所需的技术知识点我也会偶尔引用一些别人的优秀文章的链接。 -该文档涉及的主要内容包括: Java、 数据结构与算法、计算机网络与数据通信、 操作系统、主流框架、数据存储、架构、面试必备知识点等等。相信不论你是前端还是后端都能在这份文档中收获到东西。 -## 关于转载 +初始想法源于自己的个人那一段比较迷茫的学习经历。主要目的是为了通过这个开源平台来帮助一些在学习 Java 或者面试过程中遇到问题的小伙伴。 -**如果需要引用到本仓库的一些东西,必须注明转载地址!!!毕竟大多都是手敲的,或者引用的是我的原创文章,希望大家尊重一下作者的劳动**:smiley::smiley::smiley:! +### 联系我 -## 如何对该开源文档进行贡献 +添加我的微信备注“Github”,回复关键字 **“加群”** 即可入群。 -1. 笔记内容大多是手敲,所以难免会有笔误。 -2. 你对其他知识点的补充。 +![我的微信](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/JavaGuide.jpg) -## 为什么要做这个开源文档? +### Contributor -在我们学习Java的时候,很多人会面临我不知道继续学什么或者面试会问什么的尴尬情况(我本人之前就很迷茫:smile:)。所以,我决定通过这个开源平台来帮助一些有需要的人,通过下面的内容,你会掌握系统的Java学习以及面试的相关知识。本来是想通过Gitbook的形式来制作的,后来想了想觉得可能有点大题小做 :grin: 。另外,我自己一个人的力量毕竟有限,希望各位有想法的朋友可以提issue。 +下面是笔主收集的一些对本仓库提过有价值的pr或者issue的朋友,人数较多,如果你也对本仓库提过不错的pr或者issue的话,你可以加我的微信与我联系。下面的排名不分先后! -## 最后 + + + + + + + + + + + + + + + + +" + + + + + + + + + + + + -本人会利用业余时间一直更新下去,目前还有很多地方不完善,一些知识点我会原创总结,还有一些知识点如果说网上有比较好的文章了,我会把这些文章加入进去。您也可以关注我的微信公众号:“Java面试通关手册”,我会在这里分享一些自己的原创文章。 另外该文档格式参考:[Github Markdown格式](https://guides.github.com/features/mastering-markdown/),表情素材来自:[EMOJI CHEAT SHEET](https://www.webpagefx.com/tools/emoji-cheat-sheet/)。 +### 公众号 +如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。 +**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java面试突击"** 即可免费领取! -**你若盛开,清风自来。 欢迎关注我的微信公众号:“Java面试通关手册”,一个有温度的微信公众号。公众号有大量资料,回复关键字“1”你可能看到想要的东西哦!:** +**Java工程师必备学习资源:** 一些Java工程师常用学习资源[公众号](#公众号)后台回复关键字 **“1”** 即可免费无套路获取。 -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-17/29079091.jpg) +![我的公众号](https://user-gold-cdn.xitu.io/2018/11/28/167598cd2e17b8ec?w=258&h=258&f=jpeg&s=27334) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" new file mode 100644 index 00000000000..4e8279e7d7d --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringBean.md" @@ -0,0 +1,451 @@ + + +- [前言](#前言) +- [一 bean的作用域](#一-bean的作用域) + - [1. singleton——唯一 bean 实例](#1-singleton——唯一-bean-实例) + - [2. prototype——每次请求都会创建一个新的 bean 实例](#2-prototype——每次请求都会创建一个新的-bean-实例) + - [3. request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效](#3-request——每一次http请求都会产生一个新的bean,该bean仅在当前http-request内有效) + - [4. session——每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效](#4-session——每一次http请求都会产生一个新的-bean,该bean仅在当前-http-session-内有效) + - [5. globalSession](#5-globalsession) +- [二 bean的生命周期](#二-bean的生命周期) + - [initialization 和 destroy](#initialization-和-destroy) + - [实现*Aware接口 在Bean中使用Spring框架的一些对象](#实现aware接口-在bean中使用spring框架的一些对象) + - [BeanPostProcessor](#beanpostprocessor) + - [总结](#总结) + - [单例管理的对象](#单例管理的对象) + - [非单例管理的对象](#非单例管理的对象) +- [三 说明](#三-说明) + + + +# 前言 +在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。 + +**Spring中的bean默认都是单例的,这些单例Bean在多线程程序下如何保证线程安全呢?** 例如对于Web应用来说,Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求,引入Spring框架之后,每个Action都是单例的,那么对于Spring托管的单例Service Bean,如何保证其安全呢? **Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器内只有一个,Java的单例是基于 JVM,每个 JVM 内只有一个实例。** + +在大多数情况下。单例 bean 是很理想的方案。不过,有时候你可能会发现你所使用的类是易变的,它们会保持一些状态,因此重用是不安全的。在这种情况下,将 class 声明为单例的就不是那么明智了。因为对象会被污染,稍后重用的时候会出现意想不到的问题。所以 Spring 定义了多种作用域的bean。 + +# 一 bean的作用域 + +创建一个bean定义,其实质是用该bean定义对应的类来创建真正实例的“配方”。把bean定义看成一个配方很有意义,它与class很类似,只根据一张“处方”就可以创建多个实例。不仅可以控制注入到对象中的各种依赖和配置值,还可以控制该对象的作用域。这样可以灵活选择所建对象的作用域,而不必在Java Class级定义作用域。Spring Framework支持五种作用域,分别阐述如下表。 + + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/1188352.jpg) + +五种作用域中,**request、session** 和 **global session** 三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于 web 的 Spring ApplicationContext 环境。 + + + +### 1. singleton——唯一 bean 实例 + +**当一个 bean 的作用域为 singleton,那么Spring IoC容器中只会存在一个共享的 bean 实例,并且所有对 bean 的请求,只要 id 与该 bean 定义相匹配,则只会返回bean的同一实例。** singleton 是单例类型(对应于单例模式),就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,但我们可以指定Bean节点的 `lazy-init=”true”` 来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。注意,singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ,可以这样配置: + +```xml + +``` + +也可以通过 `@Scope` 注解(它可以显示指定bean的作用范围。)的方式 + +```java +@Service +@Scope("singleton") +public class ServiceImpl{ + +} +``` + +### 2. prototype——每次请求都会创建一个新的 bean 实例 + +**当一个bean的作用域为 prototype,表示一个 bean 定义对应多个对象实例。** **prototype 作用域的 bean 会导致在每次对该 bean 请求**(将其注入到另一个 bean 中,或者以程序的方式调用容器的 getBean() 方法**)时都会创建一个新的 bean 实例。prototype 是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的 bean 应该使用 prototype 作用域,而对无状态的 bean 则应该使用 singleton 作用域。** 在 XML 中将 bean 定义成 prototype ,可以这样配置: + +```java + + 或者 + +``` +通过 `@Scope` 注解的方式实现就不做演示了。 + +### 3. request——每一次HTTP请求都会产生一个新的bean,该bean仅在当前HTTP request内有效 + +**request只适用于Web程序,每一次 HTTP 请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效,当请求结束后,该对象的生命周期即告结束。** 在 XML 中将 bean 定义成 request ,可以这样配置: + +```java + +``` + +### 4. session——每一次HTTP请求都会产生一个新的 bean,该bean仅在当前 HTTP session 内有效 + +**session只适用于Web程序,session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean,同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的 HTTP session 中根据 userPreferences 创建的实例,将不会看到这些特定于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候,在该HTTP session作用域内的bean也会被废弃掉。** + +```xml + +``` + +### 5. globalSession + +global session 作用域类似于标准的 HTTP session 作用域,不过仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念,它被所有构成某个 portlet web 应用的各种不同的 portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。 + +```xml + +``` + +# 二 bean的生命周期 + +Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情,顺序是怎样的,在容器关闭的时候,又会做哪些事情。 + +> spring版本:4.2.3.RELEASE +鉴于Spring源码是用gradle构建的,我也决定舍弃我大maven,尝试下洪菊推荐过的gradle。运行beanLifeCycle模块下的junit test即可在控制台看到如下输出,可以清楚了解Spring容器在创建,初始化和销毁Bean的时候依次做了那些事情。 + +``` +Spring容器初始化 +===================================== +调用GiraffeService无参构造函数 +GiraffeService中利用set方法设置属性值 +调用setBeanName:: Bean Name defined in context=giraffeService +调用setBeanClassLoader,ClassLoader Name = sun.misc.Launcher$AppClassLoader +调用setBeanFactory,setBeanFactory:: giraffe bean singleton=true +调用setEnvironment +调用setResourceLoader:: Resource File Name=spring-beans.xml +调用setApplicationEventPublisher +调用setApplicationContext:: Bean Definition Names=[giraffeService, org.springframework.context.annotation.CommonAnnotationBeanPostProcessor#0, com.giraffe.spring.service.GiraffeServicePostProcessor#0] +执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=giraffeService +调用PostConstruct注解标注的方法 +执行InitializingBean接口的afterPropertiesSet方法 +执行配置的init-method +执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=giraffeService +Spring容器初始化完毕 +===================================== +从容器中获取Bean +giraffe Name=李光洙 +===================================== +调用preDestroy注解标注的方法 +执行DisposableBean接口的destroy方法 +执行配置的destroy-method +Spring容器关闭 +``` + +先来看看,Spring在Bean从创建到销毁的生命周期中可能做得事情。 + + +### initialization 和 destroy + +有时我们需要在Bean属性值set好之后和Bean销毁之前做一些事情,比如检查Bean中某个属性是否被正常的设置好值了。Spring框架提供了多种方法让我们可以在Spring Bean的生命周期中执行initialization和pre-destroy方法。 + +**1.实现InitializingBean和DisposableBean接口** + +这两个接口都只包含一个方法。通过实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作,实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。 + +例子如下: + +```java +public class GiraffeService implements InitializingBean,DisposableBean { + @Override + public void afterPropertiesSet() throws Exception { + System.out.println("执行InitializingBean接口的afterPropertiesSet方法"); + } + @Override + public void destroy() throws Exception { + System.out.println("执行DisposableBean接口的destroy方法"); + } +} +``` +这种方法比较简单,但是不建议使用。因为这样会将Bean的实现和Spring框架耦合在一起。 + +**2.在bean的配置文件中指定init-method和destroy-method方法** + +Spring允许我们创建自己的 init 方法和 destroy 方法,只要在 Bean 的配置文件中指定 init-method 和 destroy-method 的值就可以在 Bean 初始化时和销毁之前执行一些操作。 + +例子如下: + +```java +public class GiraffeService { + //通过的destroy-method属性指定的销毁方法 + public void destroyMethod() throws Exception { + System.out.println("执行配置的destroy-method"); + } + //通过的init-method属性指定的初始化方法 + public void initMethod() throws Exception { + System.out.println("执行配置的init-method"); + } +} +``` + +配置文件中的配置: + +``` + + +``` + +需要注意的是自定义的init-method和post-method方法可以抛异常但是不能有参数。 + +这种方式比较推荐,因为可以自己创建方法,无需将Bean的实现直接依赖于spring的框架。 + +**3.使用@PostConstruct和@PreDestroy注解** + +除了xml配置的方式,Spring 也支持用 `@PostConstruct`和 `@PreDestroy`注解来指定 `init` 和 `destroy` 方法。这两个注解均在`javax.annotation` 包中。为了注解可以生效,需要在配置文件中定义org.springframework.context.annotation.CommonAnnotationBeanPostProcessor或context:annotation-config + +例子如下: + +```java +public class GiraffeService { + @PostConstruct + public void initPostConstruct(){ + System.out.println("执行PostConstruct注解标注的方法"); + } + @PreDestroy + public void preDestroy(){ + System.out.println("执行preDestroy注解标注的方法"); + } +} +``` + +配置文件: + +```xml + + + +``` + +### 实现*Aware接口 在Bean中使用Spring框架的一些对象 + +有些时候我们需要在 Bean 的初始化中使用 Spring 框架自身的一些对象来执行一些操作,比如获取 ServletContext 的一些参数,获取 ApplicaitionContext 中的 BeanDefinition 的名字,获取 Bean 在容器中的名字等等。为了让 Bean 可以获取到框架自身的一些对象,Spring 提供了一组名为*Aware的接口。 + +这些接口均继承于`org.springframework.beans.factory.Aware`标记接口,并提供一个将由 Bean 实现的set*方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。 +网上说,这些接口是利用观察者模式实现的,类似于servlet listeners,目前还不明白,不过这也不在本文的讨论范围内。 +介绍一些重要的Aware接口: + +- **ApplicationContextAware**: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。 +- **BeanFactoryAware**:获得BeanFactory对象,可以用来检测Bean的作用域。 +- **BeanNameAware**:获得Bean在配置文件中定义的名字。 +- **ResourceLoaderAware**:获得ResourceLoader对象,可以获得classpath中某个文件。 +- **ServletContextAware**:在一个MVC应用中可以获取ServletContext对象,可以读取context中的参数。 +- **ServletConfigAware**: 在一个MVC应用中可以获取ServletConfig对象,可以读取config中的参数。 + +```java +public class GiraffeService implements ApplicationContextAware, + ApplicationEventPublisherAware, BeanClassLoaderAware, BeanFactoryAware, + BeanNameAware, EnvironmentAware, ImportAware, ResourceLoaderAware{ + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + System.out.println("执行setBeanClassLoader,ClassLoader Name = " + classLoader.getClass().getName()); + } + @Override + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + System.out.println("执行setBeanFactory,setBeanFactory:: giraffe bean singleton=" + beanFactory.isSingleton("giraffeService")); + } + @Override + public void setBeanName(String s) { + System.out.println("执行setBeanName:: Bean Name defined in context=" + + s); + } + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + System.out.println("执行setApplicationContext:: Bean Definition Names=" + + Arrays.toString(applicationContext.getBeanDefinitionNames())); + } + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + System.out.println("执行setApplicationEventPublisher"); + } + @Override + public void setEnvironment(Environment environment) { + System.out.println("执行setEnvironment"); + } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + Resource resource = resourceLoader.getResource("classpath:spring-beans.xml"); + System.out.println("执行setResourceLoader:: Resource File Name=" + + resource.getFilename()); + } + @Override + public void setImportMetadata(AnnotationMetadata annotationMetadata) { + System.out.println("执行setImportMetadata"); + } +} +``` + +### BeanPostProcessor + +上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程, +Spring同样可以针对容器中的所有Bean,或者某些Bean定制初始化过程,只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法,postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行, postProcessAfterInitialization方法在容器中的Bean初始化之后执行。 + +例子如下: + +```java +public class CustomerBeanPostProcessor implements BeanPostProcessor { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName); + return bean; + } + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("执行BeanPostProcessor的postProcessAfterInitialization方法,beanName=" + beanName); + return bean; + } +} +``` + +要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中 + +```xml + +``` + +### 总结 + +所以。。。结合第一节控制台输出的内容,Spring Bean的生命周期是这样纸的: + +- Bean容器找到配置文件中 Spring Bean 的定义。 +- Bean容器利用Java Reflection API创建一个Bean的实例。 +- 如果涉及到一些属性值 利用set方法设置一些属性值。 +- 如果Bean实现了BeanNameAware接口,调用setBeanName()方法,传入Bean的名字。 +- 如果Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 +- 如果Bean实现了BeanFactoryAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。 +- 与上面的类似,如果实现了其他*Aware接口,就调用相应的方法。 +- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessBeforeInitialization()方法 +- 如果Bean实现了InitializingBean接口,执行afterPropertiesSet()方法。 +- 如果Bean在配置文件中的定义包含init-method属性,执行指定的方法。 +- 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象,执行postProcessAfterInitialization()方法 +- 当要销毁Bean的时候,如果Bean实现了DisposableBean接口,执行destroy()方法。 +- 当要销毁Bean的时候,如果Bean在配置文件中的定义包含destroy-method属性,执行指定的方法。 + +用图表示一下(图来源:http://www.jianshu.com/p/d00539babca5): + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/48376272.jpg) + +与之比较类似的中文版本: + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg) + + +**其实很多时候我们并不会真的去实现上面说描述的那些接口,那么下面我们就除去那些接口,针对bean的单例和非单例来描述下bean的生命周期:** + +### 单例管理的对象 + +当scope=”singleton”,即默认情况下,会在启动容器时(即实例化容器时)时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean,这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。如下配置: + +```xml + +``` + +如果想对所有的默认单例bean都应用延迟初始化,可以在根节点beans设置default-lazy-init属性为true,如下所示: + +```xml + +``` + +默认情况下,Spring 在读取 xml 文件的时候,就会创建对象。在创建对象的时候先调用构造器,然后调用 init-method 属性值中所指定的方法。对象在被销毁的时候,会调用 destroy-method 属性值中所指定的方法(例如调用Container.destroy()方法的时候)。写一个测试类,代码如下: + +```java +public class LifeBean { + private String name; + + public LifeBean(){ + System.out.println("LifeBean()构造函数"); + } + public String getName() { + return name; + } + + public void setName(String name) { + System.out.println("setName()"); + this.name = name; + } + + public void init(){ + System.out.println("this is init of lifeBean"); + } + + public void destory(){ + System.out.println("this is destory of lifeBean " + this); + } +} +``` + life.xml配置如下: + +```xml + +``` + +测试代码: + +```java +public class LifeTest { + @Test + public void test() { + AbstractApplicationContext container = + new ClassPathXmlApplicationContext("life.xml"); + LifeBean life1 = (LifeBean)container.getBean("life"); + System.out.println(life1); + container.close(); + } +} +``` + +运行结果: + +``` +LifeBean()构造函数 +this is init of lifeBean +com.bean.LifeBean@573f2bb1 +…… +this is destory of lifeBean com.bean.LifeBean@573f2bb1 +``` + +### 非单例管理的对象 + +当`scope=”prototype”`时,容器也会延迟初始化 bean,Spring 读取xml 文件的时候,并不会立刻创建对象,而是在第一次请求该 bean 时才初始化(如调用getBean方法时)。在第一次请求每一个 prototype 的bean 时,Spring容器都会调用其构造器创建这个对象,然后调用`init-method`属性值中所指定的方法。对象销毁的时候,Spring 容器不会帮我们调用任何方法,因为是非单例,这个类型的对象有很多个,Spring容器一旦把这个对象交给你之后,就不再管理这个对象了。 + +为了测试prototype bean的生命周期life.xml配置如下: + +```xml + +``` + +测试程序: + +```java +public class LifeTest { + @Test + public void test() { + AbstractApplicationContext container = new ClassPathXmlApplicationContext("life.xml"); + LifeBean life1 = (LifeBean)container.getBean("life_singleton"); + System.out.println(life1); + + LifeBean life3 = (LifeBean)container.getBean("life_prototype"); + System.out.println(life3); + container.close(); + } +} +``` + +运行结果: + +``` +LifeBean()构造函数 +this is init of lifeBean +com.bean.LifeBean@573f2bb1 +LifeBean()构造函数 +this is init of lifeBean +com.bean.LifeBean@5ae9a829 +…… +this is destory of lifeBean com.bean.LifeBean@573f2bb1 +``` + +可以发现,对于作用域为 prototype 的 bean ,其`destroy`方法并没有被调用。**如果 bean 的 scope 设为prototype时,当容器关闭时,`destroy` 方法不会被调用。对于 prototype 作用域的 bean,有一点非常重要,那就是 Spring不能对一个 prototype bean 的整个生命周期负责:容器在初始化、配置、装饰或者是装配完一个prototype实例后,将它交给客户端,随后就对该prototype实例不闻不问了。** 不管何种作用域,容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言,任何配置好的析构生命周期回调方法都将不会被调用。**清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源,都是客户端代码的职责**(让Spring容器释放被prototype作用域bean占用资源的一种可行方式是,通过使用bean的后置处理器,该处理器持有要被清除的bean的引用)。谈及prototype作用域的bean时,在某些方面你可以将Spring容器的角色看作是Java new操作的替代者,任何迟于该时间点的生命周期事宜都得交由客户端来处理。 + +**Spring 容器可以管理 singleton 作用域下 bean 的生命周期,在此作用域下,Spring 能够精确地知道bean何时被创建,何时初始化完成,以及何时被销毁。而对于 prototype 作用域的bean,Spring只负责创建,当容器创建了 bean 的实例后,bean 的实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理那些被配置成prototype作用域的bean的生命周期。** + + +# 三 说明 + +本文的完成结合了下面两篇文章,并做了相应修改: + +- https://blog.csdn.net/fuzhongmin05/article/details/73389779 +- https://yemengying.com/2016/07/14/spring-bean-life-cycle/ + +由于本文非本人独立原创,所以未声明为原创!在此说明! diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" new file mode 100644 index 00000000000..0efcd3f9534 --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/SpringMVC \345\267\245\344\275\234\345\216\237\347\220\206\350\257\246\350\247\243.md" @@ -0,0 +1,269 @@ +> 本文整理自网络,原文出处暂不知,对原文做了较大的改动,在此说明! + +### 先来看一下什么是 MVC 模式 + +MVC 是一种设计模式. + +**MVC 的原理图如下:** + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/60679444.jpg) + + + +### SpringMVC 简单介绍 + +SpringMVC 框架是以请求为驱动,围绕 Servlet 设计,将请求发给控制器,然后通过模型对象,分派器来展示请求结果视图。其中核心类是 DispatcherServlet,它是一个 Servlet,顶层是实现的Servlet接口。 + +### SpringMVC 使用 + +需要在 web.xml 中配置 DispatcherServlet 。并且需要配置 Spring 监听器ContextLoaderListener + +```xml + + + org.springframework.web.context.ContextLoaderListener + + + + springmvc + org.springframework.web.servlet.DispatcherServlet + + + + contextConfigLocation + classpath:spring/springmvc-servlet.xml + + 1 + + + springmvc + / + + +``` + +### SpringMVC 工作原理(重要) + +**简单来说:** + +客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Moder)->将得到视图对象返回给用户 + + + +**如下图所示:** +![SpringMVC运行原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49790288.jpg) + +上图的一个笔误的小问题:Spring MVC 的入口函数也就是前端控制器 DispatcherServlet 的作用是接收请求,响应结果。 + +**流程说明(重要):** + +(1)客户端(浏览器)发送请求,直接请求到 DispatcherServlet。 + +(2)DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。 + +(3)解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。 + +(4)HandlerAdapter 会根据 Handler 来调用真正的处理器开处理请求,并处理相应的业务逻辑。 + +(5)处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。 + +(6)ViewResolver 会根据逻辑 View 查找实际的 View。 + +(7)DispaterServlet 把返回的 Model 传给 View(视图渲染)。 + +(8)把 View 返回给请求者(浏览器) + + + +### SpringMVC 重要组件说明 + + +**1、前端控制器DispatcherServlet(不需要工程师开发),由框架提供(重要)** + +作用:**Spring MVC 的入口函数。接收请求,响应结果,相当于转发器,中央处理器。有了 DispatcherServlet 减少了其它组件之间的耦合度。用户请求到达前端控制器,它就相当于mvc模式中的c,DispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,DispatcherServlet的存在降低了组件之间的耦合性。** + +**2、处理器映射器HandlerMapping(不需要工程师开发),由框架提供** + +作用:根据请求的url查找Handler。HandlerMapping负责根据用户请求找到Handler即处理器(Controller),SpringMVC提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。 + +**3、处理器适配器HandlerAdapter** + +作用:按照特定规则(HandlerAdapter要求的规则)去执行Handler +通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。 + +**4、处理器Handler(需要工程师开发)** + +注意:编写Handler时按照HandlerAdapter的要求去做,这样适配器才可以去正确执行Handler +Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。 +由于Handler涉及到具体的用户业务请求,所以一般情况需要工程师根据业务需求开发Handler。 + +**5、视图解析器View resolver(不需要工程师开发),由框架提供** + +作用:进行视图解析,根据逻辑视图名解析成真正的视图(view) +View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。 springmvc框架提供了很多的View视图类型,包括:jstlView、freemarkerView、pdfView等。 +一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由工程师根据业务需求开发具体的页面。 + +**6、视图View(需要工程师开发)** + +View是一个接口,实现类支持不同的View类型(jsp、freemarker、pdf...) + +**注意:处理器Handler(也就是我们平常说的Controller控制器)以及视图层view都是需要我们自己手动开发的。其他的一些组件比如:前端控制器DispatcherServlet、处理器映射器HandlerMapping、处理器适配器HandlerAdapter等等都是框架提供给我们的,不需要自己手动开发。** + +### DispatcherServlet详细解析 + +首先看下源码: + +```java +package org.springframework.web.servlet; + +@SuppressWarnings("serial") +public class DispatcherServlet extends FrameworkServlet { + + public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver"; + public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver"; + public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver"; + public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping"; + public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter"; + public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"; + public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator"; + public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver"; + public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager"; + public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT"; + public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER"; + public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER"; + public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE"; + public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP"; + public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP"; + public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER"; + public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION"; + public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; + private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties"; + protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); + private static final Properties defaultStrategies; + static { + try { + ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); + defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); + } + catch (IOException ex) { + throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage()); + } + } + + /** Detect all HandlerMappings or just expect "handlerMapping" bean? */ + private boolean detectAllHandlerMappings = true; + + /** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */ + private boolean detectAllHandlerAdapters = true; + + /** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */ + private boolean detectAllHandlerExceptionResolvers = true; + + /** Detect all ViewResolvers or just expect "viewResolver" bean? */ + private boolean detectAllViewResolvers = true; + + /** Throw a NoHandlerFoundException if no Handler was found to process this request? **/ + private boolean throwExceptionIfNoHandlerFound = false; + + /** Perform cleanup of request attributes after include request? */ + private boolean cleanupAfterInclude = true; + + /** MultipartResolver used by this servlet */ + private MultipartResolver multipartResolver; + + /** LocaleResolver used by this servlet */ + private LocaleResolver localeResolver; + + /** ThemeResolver used by this servlet */ + private ThemeResolver themeResolver; + + /** List of HandlerMappings used by this servlet */ + private List handlerMappings; + + /** List of HandlerAdapters used by this servlet */ + private List handlerAdapters; + + /** List of HandlerExceptionResolvers used by this servlet */ + private List handlerExceptionResolvers; + + /** RequestToViewNameTranslator used by this servlet */ + private RequestToViewNameTranslator viewNameTranslator; + + private FlashMapManager flashMapManager; + + /** List of ViewResolvers used by this servlet */ + private List viewResolvers; + + public DispatcherServlet() { + super(); + } + + public DispatcherServlet(WebApplicationContext webApplicationContext) { + super(webApplicationContext); + } + @Override + protected void onRefresh(ApplicationContext context) { + initStrategies(context); + } + + protected void initStrategies(ApplicationContext context) { + initMultipartResolver(context); + initLocaleResolver(context); + initThemeResolver(context); + initHandlerMappings(context); + initHandlerAdapters(context); + initHandlerExceptionResolvers(context); + initRequestToViewNameTranslator(context); + initViewResolvers(context); + initFlashMapManager(context); + } +} + +``` + +DispatcherServlet类中的属性beans: + +- HandlerMapping:用于handlers映射请求和一系列的对于拦截器的前处理和后处理,大部分用@Controller注解。 +- HandlerAdapter:帮助DispatcherServlet处理映射请求处理程序的适配器,而不用考虑实际调用的是 哪个处理程序。- - - +- ViewResolver:根据实际配置解析实际的View类型。 +- ThemeResolver:解决Web应用程序可以使用的主题,例如提供个性化布局。 +- MultipartResolver:解析多部分请求,以支持从HTML表单上传文件。- +- FlashMapManager:存储并检索可用于将一个请求属性传递到另一个请求的input和output的FlashMap,通常用于重定向。 + +在Web MVC框架中,每个DispatcherServlet都拥自己的WebApplicationContext,它继承了ApplicationContext。WebApplicationContext包含了其上下文和Servlet实例之间共享的所有的基础框架beans。 + +**HandlerMapping** + +![HandlerMapping](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/96666164.jpg) + +HandlerMapping接口处理请求的映射HandlerMapping接口的实现类: + +- SimpleUrlHandlerMapping类通过配置文件把URL映射到Controller类。 +- DefaultAnnotationHandlerMapping类通过注解把URL映射到Controller类。 + +**HandlerAdapter** + + +![HandlerAdapter](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/91433100.jpg) + +HandlerAdapter接口-处理请求映射 + +AnnotationMethodHandlerAdapter:通过注解,把请求URL映射到Controller类的方法上。 + +**HandlerExceptionResolver** + + +![HandlerExceptionResolver](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/50343885.jpg) + +HandlerExceptionResolver接口-异常处理接口 + +- SimpleMappingExceptionResolver通过配置文件进行异常处理。 +- AnnotationMethodHandlerExceptionResolver:通过注解进行异常处理。 + +**ViewResolver** + +![ViewResolver](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-11/49497279.jpg) + +ViewResolver接口解析View视图。 + +UrlBasedViewResolver类 通过配置文件,把一个视图名交给到一个View来处理。 diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" index 732717aa3a6..dc56d44af82 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/Spring\345\255\246\344\271\240\344\270\216\351\235\242\350\257\225.md" @@ -116,7 +116,7 @@ Spring IOC的初始化过程: > ## Spring源码阅读 -阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己字面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 +阅读源码不仅可以加深我们对Spring设计思想的理解,提高自己的编码水品,还可以让自己在面试中如鱼得水。下面的是Github上的一个开源的Spring源码阅读,大家有时间可以看一下,当然你如果有时间也可以自己慢慢研究源码。 ### [Spring源码阅读](https://github.com/seaswalker/Spring) - [spring-core](https://github.com/seaswalker/Spring/blob/master/note/Spring.md) diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" index c23244c37d4..fa6d2b6568f 100644 --- "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper.md" @@ -28,7 +28,7 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 **ZooKeeper 是一个典型的分布式数据一致性解决方案,分布式应用程序可以基于 ZooKeeper 实现诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能。** -**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。 +**Zookeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心(提供发布订阅服务)。** 服务生产者将自己提供的服务注册到Zookeeper中心,服务的消费者在进行服务调用的时候先到Zookeeper中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。如下图所示,在 Dubbo架构中 Zookeeper 就担任了注册中心这一角色。 ![Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/35571782.jpg) @@ -41,13 +41,11 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 **为什么最好使用奇数台服务器构成 ZooKeeper 集群?** -我们知道在Zookeeper中 Leader 选举算法采用了Zab协议。Zab核心思想是当多数 Server 写成功,则任务数据写成功。 +所谓的zookeeper容错是指,当宕掉几个zookeeper服务器之后,剩下的个数必须大于宕掉的个数的话整个zookeeper才依然可用。假如我们的集群中有n台zookeeper服务器,那么也就是剩下的服务数必须大于n/2。先说一下结论,2n和2n-1的容忍度是一样的,都是n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学问题了。 +比如假如我们有3台,那么最大允许宕掉1台zookeeper服务器,如果我们有4台的的时候也同样只允许宕掉1台。 +假如我们有5台,那么最大允许宕掉2台zookeeper服务器,如果我们有6台的的时候也同样只允许宕掉2台。 -①如果有3个Server,则最多允许1个Server 挂掉。 - -②如果有4个Server,则同样最多允许1个Server挂掉。 - -既然3个或者4个Server,同样最多允许1个Server挂掉,那么它们的可靠性是一样的,所以选择奇数个ZooKeeper Server即可,这里选择3个Server。12341234 +综上,何必增加那一个不必要的zookeeper呢? @@ -60,7 +58,7 @@ ZooKeeper 是一个开源的分布式协调服务,ZooKeeper框架最初是在 - **ZooKeeper 将数据保存在内存中,这也就保证了 高吞吐量和低延迟**(但是内存限制了能够存储的容量不太大,此限制也是保持znode中存储的数据量较小的进一步原因)。 - **ZooKeeper 是高性能的。 在“读”多于“写”的应用程序中尤其地高性能,因为“写”会导致所有的服务器间同步状态。**(“读”多于“写”是协调服务的典型场景。) - **ZooKeeper有临时节点的概念。 当创建临时节点的客户端会话一直保持活动,瞬时节点就一直存在。而当会话终结时,瞬时节点被删除。持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。** -- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提交数据节点监听服务。 +- ZooKeeper 底层其实只提供了两个功能:①管理(存储、读取)用户程序提交的数据;②为用户程序提供数据节点监听服务。 **下面关于会话(Session)、 Znode、版本、Watcher、ACL概念的总结都在《从Paxos到Zookeeper 》第四章第一节以及第七章第八节有提到,感兴趣的可以看看!** @@ -76,11 +74,11 @@ Session 指的是 ZooKeeper 服务器与客户端会话。**在 ZooKeeper 中 Zookeeper将所有数据存储在内存中,数据模型是一棵树(Znode Tree),由斜杠(/)的进行分割的路径,就是一个Znode,例如/foo/path1。每个上都会保存自己的数据内容,同时还会保存一系列属性信息。 -**在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。**另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。 +**在Zookeeper中,node可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在Zookeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。** 另外,ZooKeeper还允许用户为每个节点添加一个特殊的属性:**SEQUENTIAL**.一旦节点被标记上这个属性,那么在这个节点被创建的时候,Zookeeper会自动在其节点名后面追加上一个整型数字,这个整型数字是一个由父节点维护的自增数字。 ### 2.4 版本 -在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 cversion(当前ZNode的ACL版本)。 +在前面我们已经提到,Zookeeper 的每个 ZNode 上都会存储数据,对应于每个ZNode,Zookeeper 都会为其维护一个叫作 **Stat** 的数据结构,Stat 中记录了这个 ZNode 的三个数据版本,分别是version(当前ZNode的版本)、cversion(当前ZNode子节点的版本)和 aversion(当前ZNode的ACL版本)。 ### 2.5 Watcher @@ -120,7 +118,7 @@ ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相 上图中每一个Server代表一个安装Zookeeper服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,并且每台服务器之间都互相保持着通信。集群间通过 Zab 协议(Zookeeper Atomic Broadcast)来保持数据的一致性。 -###4.3 顺序访问 +### 4.3 顺序访问 **对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号,这个编号反应了所有事务操作的先后顺序,应用程序可以使用 ZooKeeper 这个特性来实现更高层次的同步原语。** **这个编号也叫做时间戳——zxid(Zookeeper Transaction Id)** @@ -138,7 +136,16 @@ ZooKeeper 允许分布式进程通过共享的层次结构命名空间进行相 **ZooKeeper 集群中的所有机器通过一个 Leader 选举过程来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不参与 Leader 的选举过程,也不参与写操作的“过半写成功”策略,因此 Observer 机器可以在不影响写性能的情况下提升集群的读性能。** -![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-10/77341396.jpg) +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-13/91622395.jpg) + +**当 Leader 服务器出现网络中断、崩溃退出与重启等异常情况时,ZAB 协议就会进人恢复模式并选举产生新的Leader服务器。这个过程大致是这样的:** + +1. Leader election(选举阶段):节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。 +2. Discovery(发现阶段):在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。 +3. Synchronization(同步阶段):同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。同步完成之后 +准 leader 才会成为真正的 leader。 +4. Broadcast(广播阶段) +到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。 ## 六 ZooKeeper &ZAB 协议&Paxos算法 diff --git "a/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" new file mode 100644 index 00000000000..401c752f049 --- /dev/null +++ "b/\344\270\273\346\265\201\346\241\206\346\236\266/ZooKeeper\346\225\260\346\215\256\346\250\241\345\236\213\345\222\214\345\270\270\350\247\201\345\221\275\344\273\244.md" @@ -0,0 +1,202 @@ + + +- [ZooKeeper 数据模型](#zookeeper-数据模型) +- [ZNode\(数据节点\)的结构](#znode数据节点的结构) +- [测试 ZooKeeper 中的常见操作](#测试-zookeeper-中的常见操作) + - [连接 ZooKeeper 服务](#连接-zookeeper-服务) + - [查看常用命令\(help 命令\)](#查看常用命令help-命令) + - [创建节点\(create 命令\)](#创建节点create-命令) + - [更新节点数据内容\(set 命令\)](#更新节点数据内容set-命令) + - [获取节点的数据\(get 命令\)](#获取节点的数据get-命令) + - [查看某个目录下的子节点\(ls 命令\)](#查看某个目录下的子节点ls-命令) + - [查看节点状态\(stat 命令\)](#查看节点状态stat-命令) + - [查看节点信息和状态\(ls2 命令\)](#查看节点信息和状态ls2-命令) + - [删除节点\(delete 命令\)](#删除节点delete-命令) +- [参考](#参考) + + + +> 看本文之前如果你没有安装 ZooKeeper 的话,可以参考这篇文章:[《使用 SpringBoot+Dubbo 搭建一个简单分布式服务》](https://github.com/Snailclimb/springboot-integration-examples/blob/master/md/springboot-dubbo.md) 的 “开始实战 1 :zookeeper 环境安装搭建” 这部分进行安装(Centos7.4 环境下)。如果你想对 ZooKeeper 有一个整体了解的话,可以参考这篇文章:[《可能是把 ZooKeeper 概念讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/ZooKeeper.md) + +### ZooKeeper 数据模型 + +ZNode(数据节点)是 ZooKeeper 中数据的最小单元,每个ZNode上都可以保存数据,同时还是可以有子节点(这就像树结构一样,如下图所示)。可以看出,节点路径标识方式和Unix文件 +系统路径非常相似,都是由一系列使用斜杠"/"进行分割的路径表示,开发人员可以向这个节点中写人数据,也可以在节点下面创建子节点。这些操作我们后面都会介绍到。 +![ZooKeeper 数据模型](https://images.gitbook.cn/95a192b0-1c56-11e9-9a8e-f3b01b1ea9aa) + +提到 ZooKeeper 数据模型,还有一个不得不得提的东西就是 **事务 ID** 。事务的ACID(Atomic:原子性;Consistency:一致性;Isolation:隔离性;Durability:持久性)四大特性我在这里就不多说了,相信大家也已经挺腻了。 + +在Zookeeper中,事务是指能够改变 ZooKeeper 服务器状态的操作,我们也称之为事务操作或更新操作,一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求,**ZooKeeper 都会为其分配一个全局唯一的事务ID,用 ZXID 来表示**,通常是一个64位的数字。每一个ZXID对应一次更新操作,**从这些 ZXID 中可以间接地识别出Zookeeper处理这些更新操作请求的全局顺序**。 + + + +### ZNode(数据节点)的结构 + +每个 ZNode 由2部分组成: + +- stat:状态信息 +- data:数据内容 + +如下所示,我通过 get 命令来获取 根目录下的 dubbo 节点的内容。(get 命令在下面会介绍到) + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 6] get /dubbo +# 该数据节点关联的数据内容为空 +null +# 下面是该数据节点的一些状态信息,其实就是 Stat 对象的格式化输出 +cZxid = 0x2 +ctime = Tue Nov 27 11:05:34 CST 2018 +mZxid = 0x2 +mtime = Tue Nov 27 11:05:34 CST 2018 +pZxid = 0x3 +cversion = 1 +dataVersion = 0 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 0 +numChildren = 1 + +``` +这些状态信息其实就是 Stat 对象的格式化输出。Stat 类中包含了一个数据节点的所有状态信息的字段,包括事务ID、版本信息和子节点个数等,如下图所示(图源:《从Paxos到Zookeeper 分布式一致性原理与实践》,下面会介绍通过 stat 命令查看数据节点的状态)。 + +**Stat 类:** + +![Stat 类](https://images.gitbook.cn/a841e740-1c55-11e9-b5b7-abf0ec0c666a) + +关于数据节点的状态信息说明(也就是对Stat 类中的各字段进行说明),可以参考下图(图源:《从Paxos到Zookeeper 分布式一致性原理与实践》)。 + +![数据节点的状态信息说明](https://images.gitbook.cn/f44d8630-1c55-11e9-b5b7-abf0ec0c666a) + +### 测试 ZooKeeper 中的常见操作 + + +#### 连接 ZooKeeper 服务 + +进入安装 ZooKeeper文件夹的 bin 目录下执行下面的命令连接 ZooKeeper 服务(Linux环境下)(连接之前首选要确定你的 ZooKeeper 服务已经启动成功)。 + +```shell +./zkCli.sh -server 127.0.0.1:2181 +``` +![连接 ZooKeeper 服务](https://images.gitbook.cn/153b84c0-1c59-11e9-9a8e-f3b01b1ea9aa) + +从上图可以看出控制台打印出了很多信息,包括我们的主机名称、JDK 版本、操作系统等等。如果你成功看到这些信息,说明你成功连接到 ZooKeeper 服务。 + +#### 查看常用命令(help 命令) + +help 命令查看 zookeeper 常用命令 + +![help 命令](https://images.gitbook.cn/091db640-1c59-11e9-b5b7-abf0ec0c666a) + +#### 创建节点(create 命令) + +通过 create 命令在根目录创建了node1节点,与它关联的字符串是"node1" + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1” +``` +通过 create 命令在根目录创建了node1节点,与它关联的内容是数字 123 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123 +Created /node1/node1.1 +``` + +#### 更新节点数据内容(set 命令) + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1" +``` + +#### 获取节点的数据(get 命令) + +get 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过set 命令已经将节点数据内容改为 "set node1"。 + +```shell +set node1 +cZxid = 0x47 +ctime = Sun Jan 20 10:22:59 CST 2019 +mZxid = 0x4b +mtime = Sun Jan 20 10:41:10 CST 2019 +pZxid = 0x4a +cversion = 1 +dataVersion = 1 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 9 +numChildren = 1 + +``` + +#### 查看某个目录下的子节点(ls 命令) + +通过 ls 命令查看根目录下的节点 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 37] ls / +[dubbo, zookeeper, node1] +``` +通过 ls 命令查看 node1 目录下的节点 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1 +[node1.1] +``` +zookeeper 中的 ls 命令和 linux 命令中的 ls 类似, 这个命令将列出绝对路径path下的所有子节点信息(列出1级,并不递归) + +#### 查看节点状态(stat 命令) + +通过 stat 命令查看节点状态 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1 +cZxid = 0x47 +ctime = Sun Jan 20 10:22:59 CST 2019 +mZxid = 0x47 +mtime = Sun Jan 20 10:22:59 CST 2019 +pZxid = 0x4a +cversion = 1 +dataVersion = 0 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 11 +numChildren = 1 +``` +上面显示的一些信息比如cversion、aclVersion、numChildren等等,我在上面 “ZNode(数据节点)的结构” 这部分已经介绍到。 + +#### 查看节点信息和状态(ls2 命令) + + +ls2 命令更像是 ls 命令和 stat 命令的结合。ls2 命令返回的信息包括2部分:子节点列表 + 当前节点的stat信息。 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 7] ls2 /node1 +[node1.1] +cZxid = 0x47 +ctime = Sun Jan 20 10:22:59 CST 2019 +mZxid = 0x47 +mtime = Sun Jan 20 10:22:59 CST 2019 +pZxid = 0x4a +cversion = 1 +dataVersion = 0 +aclVersion = 0 +ephemeralOwner = 0x0 +dataLength = 11 +numChildren = 1 + +``` + +#### 删除节点(delete 命令) + +这个命令很简单,但是需要注意的一点是如果你要删除某一个节点,那么这个节点必须无子节点才行。 + +```shell +[zk: 127.0.0.1:2181(CONNECTED) 3] delete /node1/node1.1 +``` + +在后面我会介绍到 Java 客户端 API的使用以及开源 Zookeeper 客户端 ZkClient 和 Curator 的使用。 + + +### 参考 + +- 《从Paxos到Zookeeper 分布式一致性原理与实践》 + diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" new file mode 100644 index 00000000000..9f3ae871ee4 --- /dev/null +++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/Shell.md" @@ -0,0 +1,557 @@ + + + +- [Shell 编程入门](#shell-编程入门) + - [走进 Shell 编程的大门](#走进-shell-编程的大门) + - [为什么要学Shell?](#为什么要学shell) + - [什么是 Shell?](#什么是-shell) + - [Shell 编程的 Hello World](#shell-编程的-hello-world) + - [Shell 变量](#shell-变量) + - [Shell 编程中的变量介绍](#shell-编程中的变量介绍) + - [Shell 字符串入门](#shell-字符串入门) + - [Shell 字符串常见操作](#shell-字符串常见操作) + - [Shell 数组](#shell-数组) + - [Shell 基本运算符](#shell-基本运算符) + - [算数运算符](#算数运算符) + - [关系运算符](#关系运算符) + - [逻辑运算符](#逻辑运算符) + - [布尔运算符](#布尔运算符) + - [字符串运算符](#字符串运算符) + - [文件相关运算符](#文件相关运算符) + - [shell流程控制](#shell流程控制) + - [if 条件语句](#if-条件语句) + - [for 循环语句](#for-循环语句) + - [while 语句](#while-语句) + - [shell 函数](#shell-函数) + - [不带参数没有返回值的函数](#不带参数没有返回值的函数) + - [有返回值的函数](#有返回值的函数) + - [带参数的函数](#带参数的函数) + + + +# Shell 编程入门 + +## 走进 Shell 编程的大门 + +### 为什么要学Shell? + +学一个东西,我们大部分情况都是往实用性方向着想。从工作角度来讲,学习 Shell 是为了提高我们自己工作效率,提高产出,让我们在更少的时间完成更多的事情。 + +很多人会说 Shell 编程属于运维方面的知识了,应该是运维人员来做,我们做后端开发的没必要学。我觉得这种说法大错特错,相比于专门做Linux运维的人员来说,我们对 Shell 编程掌握程度的要求要比他们低,但是shell编程也是我们必须要掌握的! + +目前Linux系统下最流行的运维自动化语言就是Shell和Python了。 + +两者之间,Shell几乎是IT企业必须使用的运维自动化编程语言,特别是在运维工作中的服务监控、业务快速部署、服务启动停止、数据备份及处理、日志分析等环节里,shell是不可缺的。Python 更适合处理复杂的业务逻辑,以及开发复杂的运维软件工具,实现通过web访问等。Shell是一个命令解释器,解释执行用户所输入的命令和程序。一输入命令,就立即回应的交互的对话方式。 + +另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。 + +![大型互联网公司对于shell编程技能的要求](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/60190220.jpg) + +### 什么是 Shell? + +简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。 + + +W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。 +![什么是 Shell?](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-26/19456505.jpg) + + +### Shell 编程的 Hello World + +学习任何一门编程语言第一件事就是输出HelloWord了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。 + + +(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了) + +(2) 使脚本具有执行权限:`chmod +x helloworld.sh` + +(3) 使用 vim 命令修改helloworld.sh文件:`vim helloworld.sh`(vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。)) + +helloworld.sh 内容如下: + +```shell +#!/bin/bash +#第一个shell小程序,echo 是linux中的输出命令。 +echo "helloworld!" +``` + +shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会以#!开始来指定使用的 shell 类型。在linux中,除了bash shell以外,还有很多版本的shell, 例如zsh、dash等等...不过bash shell还是我们使用最多的。** + + +(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。) + +![shell 编程Hello World](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/55296212.jpg) + + +## Shell 变量 + +### Shell 编程中的变量介绍 + + +**Shell编程中一般分为三种变量:** + +1. **我们自己定义的变量(自定义变量):** 仅在当前 Shell 实例中有效,其他 Shell 启动的程序不能访问局部变量。 +2. **Linux已定义的环境变量**(环境变量, 例如:$PATH, $HOME 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。 +3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行 + +**常用的环境变量:** +> PATH 决定了shell将到哪些目录中寻找命令或程序 +HOME 当前用户主目录 +HISTSIZE 历史记录数 +LOGNAME 当前用户的登录名 +HOSTNAME 指主机的名称 +SHELL 当前用户Shell类型 +LANGUGE  语言相关的环境变量,多语言可以修改此环境变量 +MAIL 当前用户的邮件存放目录 +PS1 基本提示符,对于root用户是#,对于普通用户是$ + +**使用 Linux 已定义的环境变量:** + +比如我们要看当前用户目录可以使用:`echo $HOME`命令;如果我们要看当前用户Shell类型 可以使用`echo $SHELL`命令。可以看出,使用方法非常简单。 + +**使用自己定义的变量:** + +```shell +#!/bin/bash +#自定义变量hello +hello="hello world" +echo $hello +echo "helloworld!" +``` +![使用自己定义的变量](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/19835037.jpg) + + +**Shell 编程中的变量名的命名的注意事项:** + + +- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头,但是可以使用下划线(_)开头。 +- 中间不能有空格,可以使用下划线(_)。 +- 不能使用标点符号。 +- 不能使用bash里的关键字(可用help命令查看保留关键字)。 + + +### Shell 字符串入门 + +字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号。这点和Java中有所不同。 + +**单引号字符串:** + +```shell +#!/bin/bash +name='SnailClimb' +hello='Hello, I am '$name'!' +echo $hello +``` +输出内容: + +``` +Hello, I am SnailClimb! +``` + +**双引号字符串:** + +```shell +#!/bin/bash +name='SnailClimb' +hello="Hello, I am "$name"!" +echo $hello +``` + +输出内容: + +``` +Hello, I am SnailClimb! +``` + + +### Shell 字符串常见操作 + +**拼接字符串:** + +```shell +#!/bin/bash +name="SnailClimb" +# 使用双引号拼接 +greeting="hello, "$name" !" +greeting_1="hello, ${name} !" +echo $greeting $greeting_1 +# 使用单引号拼接 +greeting_2='hello, '$name' !' +greeting_3='hello, ${name} !' +echo $greeting_2 $greeting_3 +``` + +输出结果: + +![输出结果](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/51148933.jpg) + + +**获取字符串长度:** + +```shell +#!/bin/bash +#获取字符串长度 +name="SnailClimb" +# 第一种方式 +echo ${#name} #输出 10 +# 第二种方式 +expr length "$name"; +``` + +输出结果: +``` +10 +10 +``` + +使用 expr 命令时,表达式中的运算符左右必须包含空格,如果不包含空格,将会输出表达式本身: + +```shell +expr 5+6 // 直接输出 5+6 +expr 5 + 6 // 输出 11 +``` +对于某些运算符,还需要我们使用符号`\`进行转义,否则就会提示语法错误。 + +```shell +expr 5 * 6 // 输出错误 +expr 5 \* 6 // 输出30 +``` + +**截取子字符串:** + +简单的字符串截取: + + +```shell +#从字符串第 1 个字符开始往后截取 10 个字符 +str="SnailClimb is a great man" +echo ${str:0:10} #输出:SnailClimb +``` + +根据表达式截取: + +```shell +#!bin/bash +#author:amau + +var="http://www.runoob.com/linux/linux-shell-variable.html" + +s1=${var%%t*}#h +s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h +s3=${var%%.*}#http://www +s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html +s5=${var##*/}#linux-shell-variable.html +``` + +### Shell 数组 + +bash支持一维数组(不支持多维数组),并且没有限定数组的大小。我下面给了大家一个关于数组操作的 Shell 代码示例,通过该示例大家可以知道如何创建数组、获取数组长度、获取/删除特定位置的数组元素、删除整个数组以及遍历数组。 + + +```shell +#!/bin/bash +array=(1 2 3 4 5); +# 获取数组长度 +length=${#array[@]} +# 或者 +length2=${#array[*]} +#输出数组长度 +echo $length #输出:5 +echo $length2 #输出:5 +# 输出数组第三个元素 +echo ${array[2]} #输出:3 +unset array[1]# 删除下表为1的元素也就是删除第二个元素 +for i in ${array[@]};do echo $i ;done # 遍历数组,输出: 1 3 4 5 +unset arr_number; # 删除数组中的所有元素 +for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没有任何输出内容 +``` + + +## Shell 基本运算符 + +> 说明:图片来自《菜鸟教程》 + + Shell 编程支持下面几种运算符 + +- 算数运算符 +- 关系运算符 +- 布尔运算符 +- 字符串运算符 +- 文件测试运算符 + +### 算数运算符 + +![算数运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/4937342.jpg) + +我以加法运算符做一个简单的示例: + +```shell +#!/bin/bash +a=3;b=3; +val=`expr $a + $b` +#输出:Total value : 6 +echo "Total value : $val +``` + + +### 关系运算符 + +关系运算符只支持数字,不支持字符串,除非字符串的值是数字。 + +![shell关系运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/64391380.jpg) + +通过一个简单的示例演示关系运算符的使用,下面shell程序的作用是当score=100的时候输出A否则输出B。 + +```shell +#!/bin/bash +score=90; +maxscore=100; +if [ $score -eq $maxscore ] +then + echo "A" +else + echo "B" +fi +``` + +输出结果: + +``` +B +``` + +### 逻辑运算符 + +![逻辑运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60545848.jpg) + +示例: + +```shell +#!/bin/bash +a=$(( 1 && 0)) +# 输出:0;逻辑与运算只有相与的两边都是1,与的结果才是1;否则与的结果是0 +echo $a; +``` + +### 布尔运算符 + + +![布尔运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/93961425.jpg) + +这里就不做演示了,应该挺简单的。 + +### 字符串运算符 + +![ 字符串运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/309094.jpg) + +简单示例: + +```shell + +#!/bin/bash +a="abc"; +b="efg"; +if [ $a = $b ] +then + echo "a 等于 b" +else + echo "a 不等于 b" +fi +``` +输出: + +``` +a 不等于 b +``` + +### 文件相关运算符 + +![文件相关运算符](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60359774.jpg) + +使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。 + +## shell流程控制 + +### if 条件语句 + +简单的 if else-if else 的条件语句示例 + +```shell +#!/bin/bash +a=3; +b=9; +if [ $a = $b ] +then + echo "a 等于 b" +elif [ $a > $b ] +then + echo "a 大于 b" +else + echo "a 小于 b" +fi +``` + +输出结果: + +``` +a 大于 b +``` + +相信大家通过上面的示例就已经掌握了 shell 编程中的 if 条件语句。不过,还要提到的一点是,不同于我们常见的 Java 以及 PHP 中的 if 条件语句,shell if 条件语句中不能包含空语句也就是什么都不做的语句。 + +### for 循环语句 + +通过下面三个简单的示例认识 for 循环语句最基本的使用,实际上 for 循环语句的功能比下面你看到的示例展现的要大得多。 + +**输出当前列表中的数据:** + +```shell +for loop in 1 2 3 4 5 +do + echo "The value is: $loop" +done +``` + +**产生 10 个随机数:** + +```shell +#!/bin/bash +for i in {0..9}; +do + echo $RANDOM; +done +``` + +**输出1到5:** + +通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要,下面来看一个例子: + +```shell +#!/bin/bash +for((i=1;i<=5;i++));do + echo $i; +done; +``` + + +### while 语句 + +**基本的 while 循环语句:** + +```shell +#!/bin/bash +int=1 +while(( $int<=5 )) +do + echo $int + let "int++" +done +``` + +**while循环可用于读取键盘信息:** + +```shell +echo '按下 退出' +echo -n '输入你最喜欢的电影: ' +while read FILM +do + echo "是的!$FILM 是一个好电影" +done +``` + +输出内容: + +``` +按下 退出 +输入你最喜欢的电影: 变形金刚 +是的!变形金刚 是一个好电影 +``` + +**无线循环:** + +```shell +while true +do + command +done +``` + +## shell 函数 + +### 不带参数没有返回值的函数 + +```shell +#!/bin/bash +function(){ + echo "这是我的第一个 shell 函数!" +} +function +``` + +输出结果: + +``` +这是我的第一个 shell 函数! +``` + + +### 有返回值的函数 + +**输入两个数字之后相加并返回结果:** + +```shell +#!/bin/bash +funWithReturn(){ + echo "输入第一个数字: " + read aNum + echo "输入第二个数字: " + read anotherNum + echo "两个数字分别为 $aNum 和 $anotherNum !" + return $(($aNum+$anotherNum)) +} +funWithReturn +echo "输入的两个数字之和为 $?" +``` + +输出结果: + +``` +输入第一个数字: +1 +输入第二个数字: +2 +两个数字分别为 1 和 2 ! +输入的两个数字之和为 3 +``` + +### 带参数的函数 + + + +```shell +#!/bin/bash +funWithParam(){ + echo "第一个参数为 $1 !" + echo "第二个参数为 $2 !" + echo "第十个参数为 $10 !" + echo "第十个参数为 ${10} !" + echo "第十一个参数为 ${11} !" + echo "参数总数有 $# 个!" + echo "作为一个字符串输出所有参数 $* !" +} +funWithParam 1 2 3 4 5 6 7 8 9 34 73 + +``` + +输出结果: + +``` +第一个参数为 1 ! +第二个参数为 2 ! +第十个参数为 10 ! +第十个参数为 34 ! +第十一个参数为 73 ! +参数总数有 11 个! +作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 ! + +``` diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" index 35edec3742e..65cc9eaec0e 100644 --- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/\345\220\216\347\253\257\347\250\213\345\272\217\345\221\230\345\277\205\345\244\207\347\232\204Linux\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -1,8 +1,31 @@ + + +- [一 从认识操作系统开始](#一-从认识操作系统开始) + - [1.1 操作系统简介](#11-操作系统简介) + - [1.2 操作系统简单分类](#12-操作系统简单分类) +- [二 初探Linux](#二-初探linux) + - [2.1 Linux简介](#21-linux简介) + - [2.2 Linux诞生简介](#22-linux诞生简介) + - [2.3 Linux的分类](#23-linux的分类) +- [三 Linux文件系统概览](#三-linux文件系统概览) + - [3.1 Linux文件系统简介](#31-linux文件系统简介) + - [3.2 文件类型与目录结构](#32-文件类型与目录结构) +- [四 Linux基本命令](#四-linux基本命令) + - [4.1 目录切换命令](#41-目录切换命令) + - [4.2 目录的操作命令(增删改查)](#42-目录的操作命令增删改查) + - [4.3 文件的操作命令(增删改查)](#43-文件的操作命令增删改查) + - [4.4 压缩文件的操作命令](#44-压缩文件的操作命令) + - [4.5 Linux的权限命令](#45-linux的权限命令) + - [4.6 Linux 用户管理](#46-linux-用户管理) + - [4.7 Linux系统用户组的管理](#47-linux系统用户组的管理) + - [4.8 其他常用命令](#48-其他常用命令) + + > 学习Linux之前,我们先来简单的认识一下操作系统。 ## 一 从认识操作系统开始 -### 1.1 操作系统简介 +### 1.1 操作系统简介 我通过以下四点介绍什么操作系统: @@ -12,7 +35,7 @@ - **操作系统分内核与外壳(我们可以把外壳理解成围绕着内核的应用程序,而内核就是能操作硬件的程序)。** ![操作系统分内核与外壳](https://user-gold-cdn.xitu.io/2018/7/3/1645ee3dc5cf626e?w=862&h=637&f=png&s=23899) -### 1.2 操作系统简单分类 +### 1.2 操作系统简单分类 1. **Windows:** 目前最流行的个人桌面操作系统 ,不做多的介绍,大家都清楚。 2. **Unix:** 最早的多用户、多任务操作系统 .按照操作系统的分类,属于分时操作系统。Unix 大多被用在服务器、工作站,现在也有用在个人计算机上。它在创建互联网、计算机网络或客户端/服务器模型方面发挥着非常重要的作用。 @@ -22,7 +45,7 @@ ![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645eeb8e843f29d?w=426&h=240&f=png&s=32650) -## 二 初探Linux +## 二 初探Linux ### 2.1 Linux简介 @@ -33,7 +56,7 @@ ![Linux](https://user-gold-cdn.xitu.io/2018/7/3/1645ef0a5a4f137f?w=270&h=376&f=png&s=193487) -### 2.2 Linux诞生简介 +### 2.2 Linux诞生简介 - 1991年,芬兰的业余计算机爱好者Linus Torvalds编写了一款类似Minix的系统(基于微内核架构的类Unix操作系统)被ftp管理员命名为Linux 加入到自由软件基金的GNU计划中; - Linux以一只可爱的企鹅作为标志,象征着敢作敢为、热爱生活。 @@ -48,7 +71,7 @@ ![Linux发行版本](https://user-gold-cdn.xitu.io/2018/7/3/1645efa7048fd018?w=548&h=274&f=png&s=99213) -## 三 Linux文件系统概览 +## 三 Linux文件系统概览 ### 3.1 Linux文件系统简介 @@ -75,34 +98,34 @@ Linux文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是 - **/usr :** 用于存放系统应用程序; - **/opt:** 额外安装的可选应用程序包所放置的位置。一般情况下,我们可以把tomcat等都安装到这里; - **/proc:** 虚拟文件系统目录,是系统内存的映射。可直接访问这个目录来获取系统信息; -- **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^); -- **/sbin:** 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等; -- **/dev:** 用于存放设备文件; -- **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统; -- **/boot:** 存放用于系统引导时使用的各种文件; +- **/root:** 超级用户(系统管理员)的主目录(特权阶级^o^); +- **/sbin:** 存放二进制可执行文件,只有root才能访问。这里存放的是系统管理员使用的系统级别的管理命令和程序。如ifconfig等; +- **/dev:** 用于存放设备文件; +- **/mnt:** 系统管理员安装临时文件系统的安装点,系统提供这个目录是让用户临时挂载其他的文件系统; +- **/boot:** 存放用于系统引导时使用的各种文件; - **/lib :** 存放着和系统运行相关的库文件 ; -- **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点; -- **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等; -- **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里。 +- **/tmp:** 用于存放各种临时文件,是公用的临时文件存储点; +- **/var:** 用于存放运行时需要改变数据的文件,也是某些大文件的溢出区,比方说各种服务的日志文件(系统启动日志等。)等; +- **/lost+found:** 这个目录平时是空的,系统非正常关机而留下“无家可归”的文件(windows下叫什么.chk)就在这里。 -## 四 Linux基本命令 +## 四 Linux基本命令 下面只是给出了一些比较常用的命令。推荐一个Linux命令快查网站,非常不错,大家如果遗忘某些命令或者对某些命令不理解都可以在这里得到解决。 Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/) ### 4.1 目录切换命令 -- **`cd usr`:** 切换到该目录下usr目录 -- **`cd ..(或cd../)`:** 切换到上一层目录 -- **`cd /`:** 切换到系统根目录 -- **`cd ~`:** 切换到用户主目录 -- **`cd -`:** 切换到上一个所在目录 +- **`cd usr`:** 切换到该目录下usr目录 +- **`cd ..(或cd../)`:** 切换到上一层目录 +- **`cd /`:** 切换到系统根目录 +- **`cd ~`:** 切换到用户主目录 +- **`cd -`:** 切换到上一个操作所在目录 -### 4.2 目录的操作命令(增删改查) +### 4.2 目录的操作命令(增删改查) 1. **`mkdir 目录名称`:** 增加目录 -2. **`ls或者ll`**(ll是ls -l的缩写,ll命令以看到该目录下的所有目录和文件的详细信息):查看目录信息 +2. **`ls或者ll`**(ll是ls -l的别名,ll命令可以看到该目录下的所有目录和文件的详细信息):查看目录信息 3. **`find 目录 参数`:** 寻找目录(查) 示例: @@ -114,35 +137,35 @@ Linux命令大全:[http://man.linuxde.net/](http://man.linuxde.net/) 4. **`mv 目录名称 新目录名称`:** 修改目录的名称(改) - 注意:mv的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。 + 注意:mv的语法不仅可以对目录进行重命名而且也可以对各种文件,压缩包等进行 重命名的操作。mv命令用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中。后面会介绍到mv命令的另一个用法。 5. **`mv 目录名称 目录的新位置`:** 移动目录的位置---剪切(改) 注意:mv语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作。另外mv与cp的结果不同,mv好像文件“搬家”,文件个数并未增加。而cp对文件进行复制,文件个数增加了。 6. **`cp -r 目录名称 目录拷贝的目标位置`:** 拷贝目录(改),-r代表递归拷贝 - 注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归 + 注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归 7. **`rm [-rf] 目录`:** 删除目录(删) - 注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包 + 注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了增强大家的记忆, 无论删除任何目录或文件,都直接使用`rm -rf` 目录/文件/压缩包 -### 4.3 文件的操作命令(增删改查) +### 4.3 文件的操作命令(增删改查) 1. **`touch 文件名称`:** 文件的创建(增) 2. **`cat/more/less/tail 文件名称`** 文件的查看(查) - - **`cat`:** 只能显示最后一屏内容 - - **`more`:** 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看 - - **`less`:** 可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看 + - **`cat`:** 查看显示文件内容 + - **`more`:** 可以显示百分比,回车可以向下一行, 空格可以向下一页,q可以退出查看 + - **`less`:** 可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看 - **`tail-10` :** 查看文件的后10行,Ctrl+C结束 - 注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 + 注意:命令 tail -f 文件 可以对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,可以使用tail -f catalina-2016-11-11.log 监控 文 件的变化 3. **`vim 文件`:** 修改文件的内容(改) vim编辑器是Linux中的强大组件,是vi编辑器的加强版,vim编辑器的命令和快捷方式有很多,但此处不一一阐述,大家也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就可以了。 **在实际开发中,使用vim编辑器主要作用就是修改配置文件,下面是一般步骤:** - vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) + vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq代表写入内容并退出,即保存;输入q!代表强制退出不保存。) 4. **`rm -rf 文件`:** 删除文件(删) 同目录删除:熟记 `rm -rf` 文件 即可 @@ -183,7 +206,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g ### 4.5 Linux的权限命令 - 操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在Linux中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 **`ls -l`** 命令我们可以 查看某个目录下的文件或目录的权限 + 操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在Linux中权限一般分为读(readable)、写(writable)和执行(excutable),分为三组。分别对应文件的属主(owner),属组(group)和其他用户(other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 **`ls -l`** 命令我们可以 查看某个目录下的文件或目录的权限 示例:在随意某个目录下`ls -l` @@ -200,7 +223,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g - d: 代表目录 - -: 代表文件 -- l: 代表链接(可以认为是window中的快捷方式) +- l: 代表软链接(可以认为是window中的快捷方式) **Linux中权限分为以下几种:** @@ -230,7 +253,7 @@ Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.g | x | 可以使用cd进入目录 | - +**需要注意的是超级用户可以无视普通用户的权限,即使文件目录权限是000,依旧可以访问。** **在linux中的每个用户必须属于一个组,不能独立于组外。在linux中每个文件有所有者、所在组、其它组的概念。** - **所有者** @@ -304,7 +327,7 @@ passwd命令用于设置用户的认证信息,包括用户密码、密码过 - **`pwd`:** 显示当前所在位置 - **`grep 要搜索的字符串 要搜索的文件 --color`:** 搜索命令,--color代表高亮显示 -- **`ps -ef`/`ps aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括redis字符串的进程) +- **`ps -ef`/`ps -aux`:** 这两个命令都是查看当前系统正在运行进程,两者的区别是展示格式不同。如果想要查看特定的进程可以使用这样的格式:**`ps aux|grep redis`** (查看包括redis字符串的进程),也可使用 `pgrep redis -a`。 注意:如果直接用ps((Process Status))命令,会显示所有进程的状态,通常结合grep命令查看某进程的状态。 - **`kill -9 进程的pid`:** 杀死进程(-9 表示强制终止。) @@ -314,6 +337,8 @@ passwd命令用于设置用户的认证信息,包括用户密码、密码过 - 查看当前系统的网卡信息:ifconfig - 查看与某台机器的连接情况:ping - 查看当前系统的端口使用:netstat -an +- **net-tools 和 iproute2 :** + `net-tools`起源于BSD的TCP/IP工具箱,后来成为老版本Linux内核中配置网络功能的工具。但自2001年起,Linux社区已经对其停止维护。同时,一些Linux发行版比如Arch Linux和CentOS/RHEL 7则已经完全抛弃了net-tools,只支持`iproute2`。linux ip命令类似于ifconfig,但功能更强大,旨在替代它。更多详情请阅读[如何在Linux中使用IP命令和示例](https://linoxide.com/linux-command/use-ip-command-linux) - **`shutdown`:** `shutdown -h now`: 指定现在立即关机;`shutdown +5 "System will shutdown after 5 minutes"`:指定5分钟后关机,同时送出警告信息给登入用户。 - **`reboot`:** **`reboot`:** 重开机。**`reboot -w`:** 做个重开机的模拟(只有纪录并不会真的重开机)。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" new file mode 100644 index 00000000000..f18b4a077ee --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL Index.md" @@ -0,0 +1,112 @@ + +# 思维导图-索引篇 + +> 系列思维导图源文件(数据库+架构)以及思维导图制作软件—XMind8 破解安装,公众号后台回复:**“思维导图”** 免费领取!(下面的图片不是很清楚,原图非常清晰,另外提供给大家源文件也是为了大家根据自己需要进行修改) + +![【思维导图-索引篇】](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/70973487.jpg) + +> **下面是我补充的一些内容** + +# 为什么索引能提高查询速度 + +> 以下内容整理自: +> 地址: https://juejin.im/post/5b55b842f265da0f9e589e79 +> 作者 :Java3y + +### 先从 MySQL 的基本存储结构说起 + +MySQL的基本存储结构是页(记录都存在页里边): + +![MySQL的基本存储结构是页](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/28559421.jpg) + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/82053134.jpg) + + - **各个数据页可以组成一个双向链表** + - **每个数据页中的记录又可以组成一个单向链表** + - 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录 + - 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。 + +所以说,如果我们写select * from user where indexname = 'xxx'这样没有进行任何优化的sql语句,默认会这样做: + +1. **定位到记录所在的页:需要遍历双向链表,找到所在的页** +2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了** + +很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。 + + +### 使用索引之后 + +索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对): + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/5373082.jpg) + +要找到id为8的记录简要步骤: + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-10-2/89338047.jpg) + +很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 **“目录”** 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn)) + +其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。 + +# 关于索引其他重要的内容补充 + +> 以下内容整理自:《Java工程师修炼之道》 + + +### 最左前缀原则 + +MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而最左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下: + +``` +select * from user where name=xx and city=xx ; //可以命中索引 +select * from user where name=xx ; // 可以命中索引 +select * from user where city=xx; // 无法命中索引 +``` +这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 `city= xx and name =xx`,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的. + +由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDERBY子句也遵循此规则。 + +### 注意避免冗余索引 + +冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。 + +MySQLS.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引 + +### Mysql如何为表字段添加索引??? + +1.添加PRIMARY KEY(主键索引) + +``` +ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` ) +``` +2.添加UNIQUE(唯一索引) + +``` +ALTER TABLE `table_name` ADD UNIQUE ( `column` ) +``` + +3.添加INDEX(普通索引) + +``` +ALTER TABLE `table_name` ADD INDEX index_name ( `column` ) +``` + +4.添加FULLTEXT(全文索引) + +``` +ALTER TABLE `table_name` ADD FULLTEXT ( `column`) +``` + +5.添加多列索引 + +``` +ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` ) +``` + + +# 参考 + +- 《Java工程师修炼之道》 +- 《MySQL高性能书籍_第3版》 +- https://juejin.im/post/5b55b842f265da0f9e589e79 + diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" index e0a2fa594f7..44eb02aaa9d 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL.md" @@ -47,7 +47,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去   **MyISAM:** B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。 -   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引(非聚集索引),辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,在走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 +   **InnoDB:** 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。**在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。** **因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。** PS:整理自《Java工程师修炼之道》 详细内容可以参考: @@ -87,20 +87,20 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 ![事务的特性](https://user-gold-cdn.xitu.io/2018/5/20/1637b08b98619455?w=312&h=305&f=png&s=22430) 1. **原子性:** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; - 2. **一致性:** 执行事务前后,数据保持一致; - 3. **隔离性:** 并发访问数据库时,一个用户的事物不被其他事物所干扰,各并发事务之间数据库是独立的; - 4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。 + 2. **一致性:** 执行事务前后,数据库从一个一致性状态转换到另一个一致性状态。 + 3. **隔离性:** 并发访问数据库时,一个用户的事物不被其他事务所干扰,各并发事务之间数据库是独立的; + 4. **持久性:** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库 发生故障也不应该对其有任何影响。 **为了达到上述事务特性,数据库定义了几种不同的事务隔离级别:** -- **READ_UNCOMMITTED(未授权读取):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** -- **READ_COMMITTED(授权读取):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** -- **REPEATABLE_READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** +- **READ_UNCOMMITTED(未提交读):** 最低的隔离级别,允许读取尚未提交的数据变更,**可能会导致脏读、幻读或不可重复读** +- **READ_COMMITTED(提交读):** 允许读取并发事务已经提交的数据,**可以阻止脏读,但是幻读或不可重复读仍有可能发生** +- **REPEATABLE_READ(可重复读):** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读,但幻读仍有可能发生。** - **SERIALIZABLE(串行):** 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,**该级别可以防止脏读、不可重复读以及幻读**。但是这将严重影响程序的性能。通常情况下也不会用到该级别。 这里需要注意的是:**Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.** - 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。 + 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVCC(多版本并发控制),通过行的创建时间和行的过期时间来支持并发一致性读和回滚等特性。 详细内容可以参考: [可能是最漂亮的Spring事务管理详解](https://blog.csdn.net/qq_34337272/article/details/80394121) @@ -136,8 +136,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。; 2. **读/写分离:** 经典的数据库拆分方案,主库负责写,从库负责读; - 3. **缓存:** 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存; - 4. **垂直分区:** + 3 . **垂直分区:** **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。 @@ -148,7 +147,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 **垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; - 5. **水平分区:** + 4. **水平分区:** **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** @@ -157,7 +156,7 @@ Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去 ![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119) - 水品拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水品拆分最好分库** 。 + 水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。 水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis.md" deleted file mode 100644 index f0269c05960..00000000000 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis.md" +++ /dev/null @@ -1,194 +0,0 @@ -Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。目前,Vmware在资助着redis项目的开发和维护。 - -> ### 书籍推荐 - -**《Redis实战》** - -**《Redis设计与实现》** - -> ### 教程推荐 - -**redis官方中文版教程**:[http://www.redis.net.cn/tutorial/3501.html](http://www.redis.net.cn/tutorial/3501.html) - -**Redis 教程(菜鸟教程)**:[http://www.runoob.com/redis/redis-tutorial.html](http://www.runoob.com/redis/redis-tutorial.html) - -> ### 常见问题总结 - -就我个人而言,我觉得Redis的基本使用是我们每个Java程序员都应该会的。另外,如果需要面试的话,一些关于Redis的理论知识也需要好好的学习一下。学完Redis之后,对照着下面8点看看自己还有那些不足的地方,同时,下面7点也是面试中经常会问到的。另外,《Redis实战》、《Redis设计与实现》是我比较推荐的两本学习Redis的书籍。 - -1. **Redis的两种持久化操作以及如何保障数据安全(快照和AOF)** -2. **如何防止数据出错(Redis事务)** -3. **如何使用流水线来提升性能** -4. **Redis主从复制** -5. **Redis集群的搭建** -6. **Redis的几种淘汰策略** -7. **Redis集群宕机,数据迁移问题** -8. **Redis缓存使用有很多,怎么解决缓存雪崩和缓存穿透?** - - -## Redis常见问题分析与好文Mark - -### 什么是Redis? -> Redis 是一个使用 C 语言写成的,开源的 key-value 数据库。。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。目前,Vmware在资助着redis项目的开发和维护。 - - -### Redis与Memcached的区别与比较 -1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。 - -2 、Redis支持数据的备份,即master-slave模式的数据备份。 - -3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中 - -4、 redis的速度比memcached快很多 - -5、Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的IO复用模型。 - -![Redis与Memcached的区别与比较](https://user-gold-cdn.xitu.io/2018/4/18/162d7773080d4570?w=621&h=378&f=jpeg&s=45278) - -如果想要更详细了解的话,可以查看慕课网上的这篇手记(非常推荐) **:《脚踏两只船的困惑 - Memcached与Redis》**:[https://www.imooc.com/article/23549](https://www.imooc.com/article/23549) - -### Redis与Memcached的选择 -**终极策略:** 使用Redis的String类型做的事,都可以用Memcached替换,以此换取更好的性能提升; 除此以外,优先考虑Redis; - -### 使用redis有哪些好处? -(1) **速度快**,因为数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1) - -(2)**支持丰富数据类型**,支持string,list,set,sorted set,hash - -(3) **支持事务** :redis对事务是部分支持的,如果是在入队时报错,那么都不会执行;在非入队时报错,那么成功的就会成功执行。详细了解请参考:《Redis事务介绍(四)》:[https://blog.csdn.net/cuipeng0916/article/details/53698774](https://blog.csdn.net/cuipeng0916/article/details/53698774) - -redis监控:锁的介绍 - -(4) **丰富的特性**:可用于缓存,消息,按key设置过期时间,过期后将会自动删除 - -### Redis常见数据结构使用场景 - -#### 1. String - -> **常用命令:** set,get,decr,incr,mget 等。 - - -String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 -常规key-value缓存应用; -常规计数:微博数,粉丝数等。 - -#### 2.Hash -> **常用命令:** hget,hset,hgetall 等。 - -Hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。 - -**举个例子:** 最近做的一个电商网站项目的首页就使用了redis的hash数据结构进行缓存,因为一个网站的首页访问量是最大的,所以通常网站的首页可以通过redis缓存来提高性能和并发量。我用**jedis客户端**来连接和操作我搭建的redis集群或者单机redis,利用jedis可以很容易的对redis进行相关操作,总的来说从搭一个简单的集群到实现redis作为缓存的整个步骤不难。感兴趣的可以看我昨天写的这篇文章: - -**《一文轻松搞懂redis集群原理及搭建与使用》:** [https://juejin.im/post/5ad54d76f265da23970759d3](https://juejin.im/post/5ad54d76f265da23970759d3) - -#### 3.List -> **常用命令:** lpush,rpush,lpop,rpop,lrange等 - -list就是链表,Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,最新消息排行等功能都可以用Redis的list结构来实现。 - -Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 - - -#### 4.Set -> **常用命令:** -sadd,spop,smembers,sunion 等 - -set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的。 -当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。 - - - -在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同喜好、二度好友等功能。 - -#### 5.Sorted Set -> **常用命令:** zadd,zrange,zrem,zcard等 - - -和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 - -**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用Redis中的SortedSet结构进行存储。 - - -### MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据(redis有哪些数据淘汰策略???) - -   相关知识:redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略: -1. **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 -2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 -3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 -4. **allkeys-lru**:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰 -5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰 -6. **no-enviction**(驱逐):禁止驱逐数据 - -### Redis的并发竞争问题如何解决? - -Redis为单进程单线程模式,采用队列模式将并发访问变为串行访问。Redis本身没有锁的概念,Redis对于多个客户端连接并不存在竞争,但是在Jedis客户端对Redis进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题,这些问题均是由于客户端连接混乱造成。对此有2种解决方法: - - 1.客户端角度,为保证每个客户端间正常有序与Redis进行通信,对连接进行池化,同时对客户端读写Redis操作采用内部锁synchronized。 -  - 2.服务器角度,利用setnx实现锁。 - - 注:对于第一种,需要应用程序自己处理资源的同步,可以使用的方法比较通俗,可以使用synchronized也可以使用lock;第二种需要用到Redis的setnx命令,但是需要注意一些问题。 - - -### Redis回收进程如何工作的? Redis回收使用的是什么算法? -**Redis内存回收:LRU算法(写的很不错,推荐)**:[https://www.cnblogs.com/WJ5888/p/4371647.html](https://www.cnblogs.com/WJ5888/p/4371647.html) - -### Redis 大量数据插入 -官方文档给的解释:[http://www.redis.cn/topics/mass-insert.html](http://www.redis.cn/topics/mass-insert.html) - -### Redis 分区的优势、不足以及分区类型 -官方文档提供的讲解:[http://www.redis.net.cn/tutorial/3524.html](http://www.redis.net.cn/tutorial/3524.html) - -### Redis持久化数据和缓存怎么做扩容? - -**《redis的持久化和缓存机制》** :[https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/春夏秋冬又一春之Redis持久化.md](https://github.com/Snailclimb/Java-Guide/blob/master/数据存储/春夏秋冬又一春之Redis持久化.md) - -扩容的话可以通过redis集群实现,之前做项目的时候用过自己搭的redis集群 -然后写了一篇关于redis集群的文章:**《一文轻松搞懂redis集群原理及搭建与使用》**:[https://juejin.im/post/5ad54d76f265da23970759d3](https://juejin.im/post/5ad54d76f265da23970759d3) - -### Redis常见性能问题和解决方案: - -1. Master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件 -2. 如果数据比较重要,某个Slave开启AOF备份数据,策略设置为每秒同步一次 -3. 为了主从复制的速度和连接的稳定性,Master和Slave最好在同一个局域网内 -4. 尽量避免在压力很大的主库上增加从库 - -### Redis与消息队列 ->作者:翁伟 -链接:https://www.zhihu.com/question/20795043/answer/345073457 - -不要使用redis去做消息队列,这不是redis的设计目标。但实在太多人使用redis去做去消息队列,redis的作者看不下去,另外基于redis的核心代码,另外实现了一个消息队列disque: antirez/disque:[https://github.com/antirez/disque](https://github.com/antirez/disque)部署、协议等方面都跟redis非常类似,并且支持集群,延迟消息等等。 - -我在做网站过程接触比较多的还是使用redis做缓存,比如秒杀系统,首页缓存等等。 - - - - - -## 好文Mark -**非常非常推荐下面几篇文章。。。** - -**《Redis深入之道:原理解析、场景使用以及视频解读》**:[https://zhuanlan.zhihu.com/p/28073983](https://zhuanlan.zhihu.com/p/28073983): -主要介绍了:Redis集群开源的方案、Redis协议简介及持久化Aof文件解析、Redis短连接性能优化等等内容,文章干货太大,容量很大,建议时间充裕可以看看。另外文章里面还提供了视频讲解,可以说是非常非常用心了。 - -**《阿里云Redis混合存储典型场景:如何轻松搭建视频直播间系统》:**[https://yq.aliyun.com/articles/582487?utm_content=m_46529](https://yq.aliyun.com/articles/582487?utm_content=m_46529): -主要介绍视频直播间系统,以及如何使用阿里云Redis混合存储实例方便快捷的构建大数据量,低延迟的视频直播间服务。还介绍到了我们之前提高过的redis的数据结构的使用场景 - - -**《美团在Redis上踩过的一些坑-5.redis cluster遇到的一些问》**:[http://carlosfu.iteye.com/blog/2254573](http://carlosfu.iteye.com/blog/2254573):主要介绍了redis集群的两个常见问题,然后分享了 一些关于redis集群不错的文章。 - -**参考:** - -https://www.cnblogs.com/Survivalist/p/8119891.html - -http://www.redis.net.cn/tutorial/3524.html - -https://redis.io/ - - - - - - - - diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" new file mode 100644 index 00000000000..a53a6481a00 --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis.md" @@ -0,0 +1,300 @@ + + +- [redis 简介](#redis-简介) +- [为什么要用 redis /为什么要用缓存](#为什么要用-redis-为什么要用缓存) +- [为什么要用 redis 而不用 map/guava 做缓存?](#为什么要用-redis-而不用-mapguava-做缓存) +- [redis 和 memcached 的区别](#redis-和-memcached-的区别) +- [redis 常见数据结构以及使用场景分析](#redis-常见数据结构以及使用场景分析) + - [1. String](#1-string) + - [2.Hash](#2hash) + - [3.List](#3list) + - [4.Set](#4set) + - [5.Sorted Set](#5sorted-set) +- [redis 设置过期时间](#redis-设置过期时间) +- [redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)](#redis-内存淘汰机制(mysql里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据?)) +- [redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)](#redis-持久化机制(怎么保证-redis-挂掉之后再重启数据可以进行恢复)) +- [redis 事务](#redis-事务) +- [缓存雪崩和缓存穿透问题解决方案](#缓存雪崩和缓存穿透问题解决方案) +- [如何解决 Redis 的并发竞争 Key 问题](#如何解决-redis-的并发竞争-key-问题) +- [如何保证缓存与数据库双写时的数据一致性?](#如何保证缓存与数据库双写时的数据一致性?) +- [参考:](#参考:) + + + + +### redis 简介 + +简单来说 redis 就是一个数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以存写速度非常快,因此 redis 被广泛应用于缓存方向。另外,redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不同的业务场景。除此之外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。 + +### 为什么要用 redis /为什么要用缓存 + +主要从“高性能”和“高并发”这两点来看待这个问题。 + +**高性能:** + +假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可! + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/54316596.jpg) + + +**高并发:** + +直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。 + + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/85146760.jpg) + + +### 为什么要用 redis 而不用 map/guava 做缓存? + + +>下面的内容来自 segmentfault 一位网友的提问,地址:https://segmentfault.com/q/1010000009106416 + +缓存分为本地缓存和分布式缓存。以 Java 为例,使用自带的 map 或者 guava 实现的是本地缓存,最主要的特点是轻量以及快速,生命周期随着 jvm 的销毁而结束,并且在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。 + +使用 redis 或 memcached 之类的称为分布式缓存,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。缺点是需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。 + + +### redis 和 memcached 的区别 + +对于 redis 和 memcached 我总结了下面四点。现在公司一般都是用 redis 来实现缓存,而且 redis 自身也越来越强大了! + +1. **redis支持更丰富的数据类型(支持更复杂的应用场景)**:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。 +2. **Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。** +3. **集群模式**:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的. +4. **Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。** + + +> 来自网络上的一张图,这里分享给大家! + +![redis 和 memcached 的区别](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-24/61603179.jpg) + + +### redis 常见数据结构以及使用场景分析 + +#### 1. String + +> **常用命令:** set,get,decr,incr,mget 等。 + + +String数据结构是简单的key-value类型,value其实不仅可以是String,也可以是数字。 +常规key-value缓存应用; +常规计数:微博数,粉丝数等。 + +#### 2.Hash +> **常用命令:** hget,hset,hgetall 等。 + +Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以Hash数据结构来存储用户信息,商品信息等等。比如下面我就用 hash 类型存放了我本人的一些信息: + +``` +key=JavaUser293847 +value={ + “id”: 1, + “name”: “SnailClimb”, + “age”: 22, + “location”: “Wuhan, Hubei” +} + +``` + + +#### 3.List +> **常用命令:** lpush,rpush,lpop,rpop,lrange等 + +list 就是链表,Redis list 的应用场景非常多,也是Redis最重要的数据结构之一,比如微博的关注列表,粉丝列表,消息列表等功能都可以用Redis的 list 结构来实现。 + +Redis list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。 + +另外可以通过 lrange 命令,就是从某个元素开始读取多少个元素,可以基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西(一页一页的往下走),性能高。 + +#### 4.Set + +> **常用命令:** +sadd,spop,smembers,sunion 等 + +set 对外提供的功能与list类似是一个列表的功能,特殊之处在于 set 是可以自动排重的。 + +当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。 + +比如:在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程,具体命令如下: + +``` +sinterstore key1 key2 key3 将交集存在key1内 +``` + +#### 5.Sorted Set +> **常用命令:** zadd,zrange,zrem,zcard等 + + +和set相比,sorted set增加了一个权重参数score,使得集合中的元素能够按score进行有序排列。 + +**举例:** 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。 + + +### redis 设置过期时间 + +Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。 + +我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。 + +如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的? + +**定期删除+惰性删除。** + +通过名字大概就能猜出这两个删除方式的意思了。 + +- **定期删除**:redis默认是每隔 100ms 就**随机抽取**一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! +- **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈! + + +但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? + +**redis 内存淘汰机制。** + +### redis 内存淘汰机制(MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?) + +redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,大家可以自行查阅或者通过这个网址查看: [http://download.redis.io/redis-stable/redis.conf](http://download.redis.io/redis-stable/redis.conf) + +**redis 提供 6种数据淘汰策略:** + +1. **volatile-lru**:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰 +2. **volatile-ttl**:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 +3. **volatile-random**:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰 +4. **allkeys-lru**:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的). +5. **allkeys-random**:从数据集(server.db[i].dict)中任意选择数据淘汰 +6. **no-eviction**:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧! + + +**备注: 关于 redis 设置过期时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!** + + +### redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复) + +很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。 + +Redis不同于Memcached的很重一点就是,Redis支持持久化,而且支持两种不同的持久化操作。**Redis的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。 + +**快照(snapshotting)持久化(RDB)** + +Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。 + +快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置: + +```conf + +save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 + +save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 + +save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发BGSAVE命令创建快照。 +``` + + +**AOF(append-only file)持久化** + +与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启: + +```conf +appendonly yes +``` + +开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof。 + +在Redis的配置文件中存在三种不同的 AOF 持久化方式,它们分别是: + +```conf +appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度 +appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘 +appendfsync no #让操作系统决定何时进行同步 +``` + +为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。 + + +**Redis 4.0 对于持久化机制的优化** + +Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 + +如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。 + + + +**补充内容:AOF 重写** + +AOF重写可以产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态一样,但体积更小。 + +AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任伺读入、分析或者写入操作。 + +在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作 + + +**更多内容可以查看我的这篇文章:** + +- [https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md](https://github.com/Snailclimb/JavaGuide/blob/master/数据存储/Redis/Redis持久化.md) + + +### redis 事务 + +Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。 + +在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。 + +### 缓存雪崩和缓存穿透问题解决方案 + +**缓存雪崩** + +简介:缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决办法(中华石杉老师在他的视频中提到过,视频地址在最后一个问题中有提到): + +- 事前:尽量保证整个 redis 集群的高可用性,发现机器宕机尽快补上。选择合适的内存淘汰策略。 +- 事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉 +- 事后:利用 redis 持久化机制保存的数据尽快恢复缓存 + +![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-25/6078367.jpg) + + +**缓存穿透** + +简介:一般是黑客故意去请求缓存中不存在的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。 + +解决办法: 有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 + +参考: + +- [https://blog.csdn.net/zeb_perfect/article/details/54135506](https://blog.csdn.net/zeb_perfect/article/details/54135506) + +### 如何解决 Redis 的并发竞争 Key 问题 + +所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同! + +推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能) + +基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。 + +在实践中,当然是从以可靠性为主。所以首推Zookeeper。 + +参考: + +- https://www.jianshu.com/p/8bddd381de06 + + +### 如何保证缓存与数据库双写时的数据一致性? + + +你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题? + +一般来说,就是如果你的系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况 + +串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。 + +**参考:** + +- Java工程师面试突击第1季(可能是史上最好的Java面试突击课程)-中华石杉老师。视频地址见下面! + - 链接: https://pan.baidu.com/s/18pp6g1xKVGCfUATf_nMrOA + - 密码:5i58 + +### 参考: + +- redis设计与实现(第二版) + diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" similarity index 94% rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" index b2d30b1884b..fbad95556a0 100644 --- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\230\245\345\244\217\347\247\213\345\206\254\345\217\210\344\270\200\346\230\245\344\271\213Redis\346\214\201\344\271\205\345\214\226.md" +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" @@ -104,6 +104,11 @@ auto-aof-rewrite-min-size 64mb 随着负载量的上升,或者数据的完整性变得 越来越重要时,用户可能需要使用到复制特性。 +## Redis 4.0 对于持久化机制的优化 +Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。 + +如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分就是压缩格式不再是 AOF 格式,可读性较差。 + 参考: 《Redis实战》 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" new file mode 100644 index 00000000000..b1742f2fbf9 --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" @@ -0,0 +1,47 @@ +这篇文章主要是对 Redis 官方网站刊登的 [Distributed locks with Redis](https://redis.io/topics/distlock) 部分内容的总结和翻译。 + +## 什么是 RedLock + +Redis 官方站这篇文章提出了一种权威的基于 Redis 实现分布式锁的方式名叫 *Redlock*,此种方式比原先的单节点的方法更安全。它可以保证以下特性: + +1. 安全特性:互斥访问,即永远只有一个 client 能拿到锁 +2. 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区 +3. 容错性:只要大部分 Redis 节点存活就可以正常提供服务 + +## 怎么在单节点上实现分布式锁 + +> SET resource_name my_random_value NX PX 30000 + +主要依靠上述命令,该命令仅当 Key 不存在时(NX保证)set 值,并且设置过期时间 3000ms (PX保证),值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的,释放锁的逻辑是: + +```lua +if redis.call("get",KEYS[1]) == ARGV[1] then + return redis.call("del",KEYS[1]) +else + return 0 +end +``` + +上述实现可以避免释放另一个client创建的锁,如果只有 del 命令的话,那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间,此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2,那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了,但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string,方法很多随意选择一种就行了。 + +## Redlock 算法 + +算法很易懂,起 5 个 master 节点,分布在不同的机房尽量保证可用性。为了获得锁,client 会进行如下操作: + +1. 得到当前的时间,微妙单位 +2. 尝试顺序地在 5 个实例上申请锁,当然需要使用相同的 key 和 random value,这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小,避免长时间和一个 fail 了的节点浪费时间 +3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候,且它会计算申请锁消耗了多少时间,这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到,如果锁的持续时长(lock validity time)比流逝的时间多的话,那么锁就真正获取到了。 +4. 如果锁申请到了,那么锁真正的 lock validity time 应该是 origin(lock validity time) - 申请锁期间流逝的时间 +5. 如果 client 申请锁失败了,那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作,重置状态 + +## 失败重试 + +如果一个 client 申请锁失败了,那么它需要稍等一会在重试避免多个 client 同时申请锁的情况,最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作,便于其他 client 获得这把锁,避免这些锁过期造成的时间浪费,当然如果这时候网络分区使得 client 无法联系上这些 master,那么这种浪费就是不得不付出的代价了。 + +## 放锁 + +放锁操作很简单,就是依次释放所有节点上的锁就行了 + +## 性能、崩溃恢复和 fsync + +如果我们的节点没有持久化机制,client 从 5 个 master 中的 3 个处获得了锁,然后其中一个重启了,这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁!** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些,因为 Redis 的过期机制是语义层面实现的,所以在 server 挂了的时候时间依旧在流逝,重启之后锁状态不会受到污染。但是考虑断电之后呢,AOF部分命令没来得及刷回磁盘直接丢失了,除非我们配置刷回策略为 fsnyc = always,但这会损伤性能。解决这个问题的方法是,当一个节点重启之后,我们规定在 max TTL 期间它是不可用的,这样它就不会干扰原本已经申请到的锁,等到它 crash 前的那部分锁都过期了,环境不存在历史锁了,那么再把这个节点加进来正常工作。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" new file mode 100644 index 00000000000..043df96566d --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\345\201\232\345\217\257\351\235\240\347\232\204\345\210\206\345\270\203\345\274\217\351\224\201\357\274\214Redlock\347\234\237\347\232\204\345\217\257\350\241\214\344\271\210.md" @@ -0,0 +1,91 @@ +本文是对 [Martin Kleppmann](https://martin.kleppmann.com/) 的文章 [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html) 部分内容的翻译和总结,上次写 Redlock 的原因就是看到了 Martin 的这篇文章,写得很好,特此翻译和总结。感兴趣的同学可以翻看原文,相信会收获良多。 + +开篇作者认为现在 Redis 逐渐被使用到数据管理领域,这个领域需要更强的数据一致性和耐久性,这使得他感到担心,因为这不是 Redis 最初设计的初衷(事实上这也是很多业界程序员的误区,越来越把 Redis 当成数据库在使用),其中基于 Redis 的分布式锁就是令人担心的其一。 + +Martin 指出首先你要明确你为什么使用分布式锁,为了性能还是正确性?为了帮你区分这二者,在这把锁 fail 了的时候你可以询问自己以下问题: +1. **要性能的:** 拥有这把锁使得你不会重复劳动(例如一个 job 做了两次),如果这把锁 fail 了,两个节点同时做了这个 Job,那么这个 Job 增加了你的成本。 +2. **要正确性的:** 拥有锁可以防止并发操作污染你的系统或者数据,如果这把锁 fail 了两个节点同时操作了一份数据,结果可能是数据不一致、数据丢失、file 冲突等,会导致严重的后果。 + +上述二者都是需求锁的正确场景,但是你必须清楚自己是因为什么原因需要分布式锁。 + +如果你只是为了性能,那没必要用 Redlock,它成本高且复杂,你只用一个 Redis 实例也够了,最多加个从防止主挂了。当然,你使用单节点的 Redis 那么断电或者一些情况下,你会丢失锁,但是你的目的只是加速性能且断电这种事情不会经常发生,这并不是什么大问题。并且如果你使用了单节点 Redis,那么很显然你这个应用需要的锁粒度是很模糊粗糙的,也不会是什么重要的服务。 + +那么是否 Redlock 对于要求正确性的场景就合适呢?Martin 列举了若干场景证明 Redlock 这种算法是不可靠的。 + +## 用锁保护资源 +这节里 Martin 先将 Redlock 放在了一边而是仅讨论总体上一个分布式锁是怎么工作的。在分布式环境下,锁比 mutex 这类复杂,因为涉及到不同节点、网络通信并且他们随时可能无征兆的 fail 。 +Martin 假设了一个场景,一个 client 要修改一个文件,它先申请得到锁,然后修改文件写回,放锁。另一个 client 再申请锁 ... 代码流程如下: + +```java +// THIS CODE IS BROKEN +function writeData(filename, data) { + var lock = lockService.acquireLock(filename); + if (!lock) { + throw 'Failed to acquire lock'; + } + + try { + var file = storage.readFile(filename); + var updated = updateContents(file, data); + storage.writeFile(filename, updated); + } finally { + lock.release(); + } +} +``` + +可惜即使你的锁服务非常完美,上述代码还是可能跪,下面的流程图会告诉你为什么: + +![](https://martin.kleppmann.com/2016/02/unsafe-lock.png) + +上述图中,得到锁的 client1 在持有锁的期间 pause 了一段时间,例如 GC 停顿。锁有过期时间(一般叫租约,为了防止某个 client 崩溃之后一直占有锁),但是如果 GC 停顿太长超过了锁租约时间,此时锁已经被另一个 client2 所得到,原先的 client1 还没有感知到锁过期,那么奇怪的结果就会发生,曾经 HBase 就发生过这种 Bug。即使你在 client1 写回之前检查一下锁是否过期也无助于解决这个问题,因为 GC 可能在任何时候发生,即使是你非常不便的时候(在最后的检查与写操作期间)。 +如果你认为自己的程序不会有长时间的 GC 停顿,还有其他原因会导致你的进程 pause。例如进程可能读取尚未进入内存的数据,所以它得到一个 page fault 并且等待 page 被加载进缓存;还有可能你依赖于网络服务;或者其他进程占用 CPU;或者其他人意外发生 SIGSTOP 等。 + +... .... 这里 Martin 又增加了一节列举各种进程 pause 的例子,为了证明上面的代码是不安全的,无论你的锁服务多完美。 + +## 使用 Fencing (栅栏)使得锁变安全 +修复问题的方法也很简单:你需要在每次写操作时加入一个 fencing token。这个场景下,fencing token 可以是一个递增的数字(lock service 可以做到),每次有 client 申请锁就递增一次: + +![](https://martin.kleppmann.com/2016/02/fencing-tokens.png) + +client1 申请锁同时拿到 token33,然后它进入长时间的停顿锁也过期了。client2 得到锁和 token34 写入数据,紧接着 client1 活过来之后尝试写入数据,自身 token33 比 34 小因此写入操作被拒绝。注意这需要存储层来检查 token,但这并不难实现。如果你使用 Zookeeper 作为 lock service 的话那么你可以使用 zxid 作为递增数字。 +但是对于 Redlock 你要知道,没什么生成 fencing token 的方式,并且怎么修改 Redlock 算法使其能产生 fencing token 呢?好像并不那么显而易见。因为产生 token 需要单调递增,除非在单节点 Redis 上完成但是这又没有高可靠性,你好像需要引进一致性协议来让 Redlock 产生可靠的 fencing token。 + +## 使用时间来解决一致性 +Redlock 无法产生 fencing token 早该成为在需求正确性的场景下弃用它的理由,但还有一些值得讨论的地方。 + +学术界有个说法,算法对时间不做假设:因为进程可能pause一段时间、数据包可能因为网络延迟延后到达、时钟可能根本就是错的。而可靠的算法依旧要在上述假设下做正确的事情。 + +对于 failure detector 来说,timeout 只能作为猜测某个节点 fail 的依据,因为网络延迟、本地时钟不正确等其他原因的限制。考虑到 Redis 使用 gettimeofday,而不是单调的时钟,会受到系统时间的影响,可能会突然前进或者后退一段时间,这会导致一个 key 更快或更慢地过期。 + +可见,Redlock 依赖于许多时间假设,它假设所有 Redis 节点都能对同一个 Key 在其过期前持有差不多的时间、跟过期时间相比网络延迟很小、跟过期时间相比进程 pause 很短。 + +## 用不可靠的时间打破 Redlock +这节 Martin 举了个因为时间问题,Redlock 不可靠的例子。 + +1. client1 从 ABC 三个节点处申请到锁,DE由于网络原因请求没有到达 +2. C节点的时钟往前推了,导致 lock 过期 +3. client2 在CDE处获得了锁,AB由于网络原因请求未到达 +4. 此时 client1 和 client2 都获得了锁 + +**在 Redlock 官方文档中也提到了这个情况,不过是C崩溃的时候,Redlock 官方本身也是知道 Redlock 算法不是完全可靠的,官方为了解决这种问题建议使用延时启动,相关内容可以看之前的[这篇文章](https://zhuanlan.zhihu.com/p/40915772)。但是 Martin 这里分析得更加全面,指出延时启动不也是依赖于时钟的正确性的么?** + +接下来 Martin 又列举了进程 Pause 时而不是时钟不可靠时会发生的问题: + +1. client1 从 ABCDE 处获得了锁 +2. 当获得锁的 response 还没到达 client1 时 client1 进入 GC 停顿 +3. 停顿期间锁已经过期了 +4. client2 在 ABCDE 处获得了锁 +5. client1 GC 完成收到了获得锁的 response,此时两个 client 又拿到了同一把锁 + +**同时长时间的网络延迟也有可能导致同样的问题。** + +## Redlock 的同步性假设 +这些例子说明了,仅有在你假设了一个同步性系统模型的基础上,Redlock 才能正常工作,也就是系统能满足以下属性: + +1. 网络延时边界,即假设数据包一定能在某个最大延时之内到达 +2. 进程停顿边界,即进程停顿一定在某个最大时间之内 +3. 时钟错误边界,即不会从一个坏的 NTP 服务器处取得时间 + +## 结论 +Martin 认为 Redlock 实在不是一个好的选择,对于需求性能的分布式锁应用它太重了且成本高;对于需求正确性的应用来说它不够安全。因为它对高危的时钟或者说其他上述列举的情况进行了不可靠的假设,如果你的应用只需要高性能的分布式锁不要求多高的正确性,那么单节点 Redis 够了;如果你的应用想要保住正确性,那么不建议 Redlock,建议使用一个合适的一致性协调系统,例如 Zookeeper,且保证存在 fencing token。 diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" new file mode 100644 index 00000000000..acbfda3d7ac --- /dev/null +++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\344\270\200\345\215\203\350\241\214MySQL\345\221\275\344\273\244.md" @@ -0,0 +1,973 @@ +> 原文地址:https://shockerli.net/post/1000-line-mysql-note/ ,JavaGuide 对本文进行了简答排版,新增了目录。 +> 作者:格物 + +非常不错的总结,强烈建议保存下来,需要的时候看一看。 + + +- [基本操作](#基本操作) +- [数据库操作](#数据库操作) +- [表的操作](#表的操作) +- [数据操作](#数据操作) +- [字符集编码](#字符集编码) +- [数据类型(列类型)](#数据类型列类型) +- [列属性(列约束)](#列属性列约束) +- [建表规范](#建表规范) +- [SELECT](#select) +- [UNION](#union) +- [子查询](#子查询) +- [连接查询(join)](#连接查询join) +- [TRUNCATE](#truncate) +- [备份与还原](#备份与还原) +- [视图](#视图) +- [事务(transaction)](#事务transaction) +- [锁表](#锁表) +- [触发器](#触发器) +- [SQL编程](#sql编程) +- [存储过程](#存储过程) +- [用户和权限管理](#用户和权限管理) +- [表维护](#表维护) +- [杂项](#杂项) + + + +### 基本操作 + +```mysql +/* Windows服务 */ +-- 启动MySQL + net start mysql +-- 创建Windows服务 + sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格) +/* 连接与断开服务器 */ +mysql -h 地址 -P 端口 -u 用户名 -p 密码 +SHOW PROCESSLIST -- 显示哪些线程正在运行 +SHOW VARIABLES -- 显示系统变量信息 +``` + +### 数据库操作 + +```mysql +/* 数据库操作 */ ------------------ +-- 查看当前数据库 + SELECT DATABASE(); +-- 显示当前时间、用户名、数据库版本 + SELECT now(), user(), version(); +-- 创建库 + CREATE DATABASE[ IF NOT EXISTS] 数据库名 数据库选项 + 数据库选项: + CHARACTER SET charset_name + COLLATE collation_name +-- 查看已有库 + SHOW DATABASES[ LIKE 'PATTERN'] +-- 查看当前库信息 + SHOW CREATE DATABASE 数据库名 +-- 修改库的选项信息 + ALTER DATABASE 库名 选项信息 +-- 删除库 + DROP DATABASE[ IF EXISTS] 数据库名 + 同时删除该数据库相关的目录及其目录内容 +``` + +### 表的操作 + +```mysql +-- 创建表 + CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项] + 每个字段必须有数据类型 + 最后一个字段后不能有逗号 + TEMPORARY 临时表,会话结束时表自动消失 + 对于字段的定义: + 字段名 数据类型 [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] [COMMENT 'string'] +-- 表选项 + -- 字符集 + CHARSET = charset_name + 如果表没有设定,则使用数据库字符集 + -- 存储引擎 + ENGINE = engine_name + 表在管理数据时采用的不同的数据结构,结构不同会导致处理方式、提供的特性操作等不同 + 常见的引擎:InnoDB MyISAM Memory/Heap BDB Merge Example CSV MaxDB Archive + 不同的引擎在保存表的结构和数据时采用不同的方式 + MyISAM表文件含义:.frm表定义,.MYD表数据,.MYI表索引 + InnoDB表文件含义:.frm表定义,表空间数据和日志文件 + SHOW ENGINES -- 显示存储引擎的状态信息 + SHOW ENGINE 引擎名 {LOGS|STATUS} -- 显示存储引擎的日志或状态信息 + -- 自增起始数 + AUTO_INCREMENT = 行数 + -- 数据文件目录 + DATA DIRECTORY = '目录' + -- 索引文件目录 + INDEX DIRECTORY = '目录' + -- 表注释 + COMMENT = 'string' + -- 分区选项 + PARTITION BY ... (详细见手册) +-- 查看所有表 + SHOW TABLES[ LIKE 'pattern'] + SHOW TABLES FROM 库名 +-- 查看表机构 + SHOW CREATE TABLE 表名 (信息更详细) + DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / SHOW COLUMNS FROM 表名 [LIKE 'PATTERN'] + SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern'] +-- 修改表 + -- 修改表本身的选项 + ALTER TABLE 表名 表的选项 + eg: ALTER TABLE 表名 ENGINE=MYISAM; + -- 对表进行重命名 + RENAME TABLE 原表名 TO 新表名 + RENAME TABLE 原表名 TO 库名.表名 (可将表移动到另一个数据库) + -- RENAME可以交换两个表名 + -- 修改表的字段机构(13.1.2. ALTER TABLE语法) + ALTER TABLE 表名 操作名 + -- 操作名 + ADD[ COLUMN] 字段定义 -- 增加字段 + AFTER 字段名 -- 表示增加在该字段名后面 + FIRST -- 表示增加在第一个 + ADD PRIMARY KEY(字段名) -- 创建主键 + ADD UNIQUE [索引名] (字段名)-- 创建唯一索引 + ADD INDEX [索引名] (字段名) -- 创建普通索引 + DROP[ COLUMN] 字段名 -- 删除字段 + MODIFY[ COLUMN] 字段名 字段属性 -- 支持对字段属性进行修改,不能修改字段名(所有原有属性也需写上) + CHANGE[ COLUMN] 原字段名 新字段名 字段属性 -- 支持对字段名修改 + DROP PRIMARY KEY -- 删除主键(删除主键前需删除其AUTO_INCREMENT属性) + DROP INDEX 索引名 -- 删除索引 + DROP FOREIGN KEY 外键 -- 删除外键 +-- 删除表 + DROP TABLE[ IF EXISTS] 表名 ... +-- 清空表数据 + TRUNCATE [TABLE] 表名 +-- 复制表结构 + CREATE TABLE 表名 LIKE 要复制的表名 +-- 复制表结构和数据 + CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名 +-- 检查表是否有错误 + CHECK TABLE tbl_name [, tbl_name] ... [option] ... +-- 优化表 + OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... +-- 修复表 + REPAIR [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... [QUICK] [EXTENDED] [USE_FRM] +-- 分析表 + ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... +``` + +### 数据操作 + +```mysql +/* 数据操作 */ ------------------ +-- 增 + INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...] + -- 如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。 + -- 可同时插入多条数据记录! + REPLACE 与 INSERT 完全一样,可互换。 + INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...] +-- 查 + SELECT 字段列表 FROM 表名[ 其他子句] + -- 可来自多个表的多个字段 + -- 其他子句可以不使用 + -- 字段列表可以用*代替,表示所有字段 +-- 删 + DELETE FROM 表名[ 删除条件子句] + 没有条件子句,则会删除全部 +-- 改 + UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件] +``` + +### 字符集编码 + +```mysql +/* 字符集编码 */ ------------------ +-- MySQL、数据库、表、字段均可设置编码 +-- 数据编码与客户端编码不需一致 +SHOW VARIABLES LIKE 'character_set_%' -- 查看所有字符集编码项 + character_set_client 客户端向服务器发送数据时使用的编码 + character_set_results 服务器端将结果返回给客户端所使用的编码 + character_set_connection 连接层编码 +SET 变量名 = 变量值 + SET character_set_client = gbk; + SET character_set_results = gbk; + SET character_set_connection = gbk; +SET NAMES GBK; -- 相当于完成以上三个设置 +-- 校对集 + 校对集用以排序 + SHOW CHARACTER SET [LIKE 'pattern']/SHOW CHARSET [LIKE 'pattern'] 查看所有字符集 + SHOW COLLATION [LIKE 'pattern'] 查看所有校对集 + CHARSET 字符集编码 设置字符集编码 + COLLATE 校对集编码 设置校对集编码 +``` + +### 数据类型(列类型) + +```mysql +/* 数据类型(列类型) */ ------------------ +1. 数值类型 +-- a. 整型 ---------- + 类型 字节 范围(有符号位) + tinyint 1字节 -128 ~ 127 无符号位:0 ~ 255 + smallint 2字节 -32768 ~ 32767 + mediumint 3字节 -8388608 ~ 8388607 + int 4字节 + bigint 8字节 + int(M) M表示总位数 + - 默认存在符号位,unsigned 属性修改 + - 显示宽度,如果某个数不够定义字段时设置的位数,则前面以0补填,zerofill 属性修改 + 例:int(5) 插入一个数'123',补填后为'00123' + - 在满足要求的情况下,越小越好。 + - 1表示bool值真,0表示bool值假。MySQL没有布尔类型,通过整型0和1表示。常用tinyint(1)表示布尔型。 +-- b. 浮点型 ---------- + 类型 字节 范围 + float(单精度) 4字节 + double(双精度) 8字节 + 浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。 + 不同于整型,前后均会补填0. + 定义浮点型时,需指定总位数和小数位数。 + float(M, D) double(M, D) + M表示总位数,D表示小数位数。 + M和D的大小会决定浮点数的范围。不同于整型的固定范围。 + M既表示总位数(不包括小数点和正负号),也表示显示宽度(所有显示符号均包括)。 + 支持科学计数法表示。 + 浮点数表示近似值。 +-- c. 定点数 ---------- + decimal -- 可变长度 + decimal(M, D) M也表示总位数,D表示小数位数。 + 保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。 + 将浮点数转换为字符串来保存,每9位数字保存为4个字节。 +2. 字符串类型 +-- a. char, varchar ---------- + char 定长字符串,速度快,但浪费空间 + varchar 变长字符串,速度慢,但节省空间 + M表示能存储的最大长度,此长度是字符数,非字节数。 + 不同的编码,所占用的空间不同。 + char,最多255个字符,与编码无关。 + varchar,最多65535字符,与编码有关。 + 一条有效记录最大不能超过65535个字节。 + utf8 最大为21844个字符,gbk 最大为32766个字符,latin1 最大为65532个字符 + varchar 是变长的,需要利用存储空间保存 varchar 的长度,如果数据小于255个字节,则采用一个字节来保存长度,反之需要两个字节来保存。 + varchar 的最大有效长度由最大行大小和使用的字符集确定。 + 最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还需两个字节来存放字符串的长度,所以有效长度是64432-1-2=65532字节。 + 例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少? 答:(65535-1-2-4-30*3)/3 +-- b. blob, text ---------- + blob 二进制字符串(字节字符串) + tinyblob, blob, mediumblob, longblob + text 非二进制字符串(字符字符串) + tinytext, text, mediumtext, longtext + text 在定义时,不需要定义长度,也不会计算总长度。 + text 类型在定义时,不可给default值 +-- c. binary, varbinary ---------- + 类似于char和varchar,用于保存二进制字符串,也就是保存字节字符串而非字符字符串。 + char, varchar, text 对应 binary, varbinary, blob. +3. 日期时间类型 + 一般用整型保存时间戳,因为PHP可以很方便的将时间戳进行格式化。 + datetime 8字节 日期及时间 1000-01-01 00:00:00 到 9999-12-31 23:59:59 + date 3字节 日期 1000-01-01 到 9999-12-31 + timestamp 4字节 时间戳 19700101000000 到 2038-01-19 03:14:07 + time 3字节 时间 -838:59:59 到 838:59:59 + year 1字节 年份 1901 - 2155 +datetime YYYY-MM-DD hh:mm:ss +timestamp YY-MM-DD hh:mm:ss + YYYYMMDDhhmmss + YYMMDDhhmmss + YYYYMMDDhhmmss + YYMMDDhhmmss +date YYYY-MM-DD + YY-MM-DD + YYYYMMDD + YYMMDD + YYYYMMDD + YYMMDD +time hh:mm:ss + hhmmss + hhmmss +year YYYY + YY + YYYY + YY +4. 枚举和集合 +-- 枚举(enum) ---------- +enum(val1, val2, val3...) + 在已知的值中进行单选。最大数量为65535. + 枚举值在保存时,以2个字节的整型(smallint)保存。每个枚举值,按保存的位置顺序,从1开始逐一递增。 + 表现为字符串类型,存储却是整型。 + NULL值的索引是NULL。 + 空字符串错误值的索引值是0。 +-- 集合(set) ---------- +set(val1, val2, val3...) + create table tab ( gender set('男', '女', '无') ); + insert into tab values ('男, 女'); + 最多可以有64个不同的成员。以bigint存储,共8个字节。采取位运算的形式。 + 当创建表时,SET成员值的尾部空格将自动被删除。 +``` + +### 列属性(列约束) + +```mysql +/* 列属性(列约束) */ ------------------ +1. PRIMARY 主键 + - 能唯一标识记录的字段,可以作为主键。 + - 一个表只能有一个主键。 + - 主键具有唯一性。 + - 声明字段时,用 primary key 标识。 + 也可以在字段列表之后声明 + 例:create table tab ( id int, stu varchar(10), primary key (id)); + - 主键字段的值不能为null。 + - 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。 + 例:create table tab ( id int, stu varchar(10), age int, primary key (stu, age)); +2. UNIQUE 唯一索引(唯一约束) + 使得某字段的值也不能重复。 +3. NULL 约束 + null不是数据类型,是列的一个属性。 + 表示当前列是否可以为null,表示什么都没有。 + null, 允许为空。默认。 + not null, 不允许为空。 + insert into tab values (null, 'val'); + -- 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null +4. DEFAULT 默认值属性 + 当前字段的默认值。 + insert into tab values (default, 'val'); -- 此时表示强制使用默认值。 + create table tab ( add_time timestamp default current_timestamp ); + -- 表示将当前时间的时间戳设为默认值。 + current_date, current_time +5. AUTO_INCREMENT 自动增长约束 + 自动增长必须为索引(主键或unique) + 只能存在一个字段为自动增长。 + 默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置,或 alter table tbl auto_increment = x; +6. COMMENT 注释 + 例:create table tab ( id int ) comment '注释内容'; +7. FOREIGN KEY 外键约束 + 用于限制主表与从表数据完整性。 + alter table t1 add constraint `t1_t2_fk` foreign key (t1_id) references t2(id); + -- 将表t1的t1_id外键关联到表t2的id字段。 + -- 每个外键都有一个名字,可以通过 constraint 指定 + 存在外键的表,称之为从表(子表),外键指向的表,称之为主表(父表)。 + 作用:保持数据一致性,完整性,主要目的是控制存储在外键表(从表)中的数据。 + MySQL中,可以对InnoDB引擎使用外键约束: + 语法: + foreign key (外键字段) references 主表名 (关联字段) [主表记录删除时的动作] [主表记录更新时的动作] + 此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下,可以设置为null.前提是该外键列,没有not null。 + 可以不指定主表记录更改或更新时的动作,那么此时主表的操作被拒绝。 + 如果指定了 on update 或 on delete:在删除或更新时,有如下几个操作可以选择: + 1. cascade,级联操作。主表数据被更新(主键值更新),从表也被更新(外键值更新)。主表记录被删除,从表相关记录也被删除。 + 2. set null,设置为null。主表数据被更新(主键值更新),从表的外键被设置为null。主表记录被删除,从表相关记录外键被设置成null。但注意,要求该外键列,没有not null属性约束。 + 3. restrict,拒绝父表删除和更新。 + 注意,外键只被InnoDB存储引擎所支持。其他引擎是不支持的。 + +``` + +### 建表规范 + +```mysql +/* 建表规范 */ ------------------ + -- Normal Format, NF + - 每个表保存一个实体信息 + - 每个具有一个ID字段作为主键 + - ID主键 + 原子表 + -- 1NF, 第一范式 + 字段不能再分,就满足第一范式。 + -- 2NF, 第二范式 + 满足第一范式的前提下,不能出现部分依赖。 + 消除符合主键就可以避免部分依赖。增加单列关键字。 + -- 3NF, 第三范式 + 满足第二范式的前提下,不能出现传递依赖。 + 某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。 + 将一个实体信息的数据放在一个表内实现。 +``` + +### SELECT + +```mysql +/* SELECT */ ------------------ +SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMIT +a. select_expr + -- 可以用 * 表示所有字段。 + select * from tb; + -- 可以使用表达式(计算公式、函数调用、字段也是个表达式) + select stu, 29+25, now() from tb; + -- 可以为每个列使用别名。适用于简化列标识,避免多个列标识符重复。 + - 使用 as 关键字,也可省略 as. + select stu+10 as add10 from tb; +b. FROM 子句 + 用于标识查询来源。 + -- 可以为表起别名。使用as关键字。 + SELECT * FROM tb1 AS tt, tb2 AS bb; + -- from子句后,可以同时出现多个表。 + -- 多个表会横向叠加到一起,而数据会形成一个笛卡尔积。 + SELECT * FROM tb1, tb2; + -- 向优化符提示如何选择索引 + USE INDEX、IGNORE INDEX、FORCE INDEX + SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3; + SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3; +c. WHERE 子句 + -- 从from获得的数据源中进行筛选。 + -- 整型1表示真,0表示假。 + -- 表达式由运算符和运算数组成。 + -- 运算数:变量(字段)、值、函数返回值 + -- 运算符: + =, <=>, <>, !=, <=, <, >=, >, !, &&, ||, + in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor + is/is not 加上ture/false/unknown,检验某个值的真假 + <=>与<>功能相同,<=>可用于null比较 +d. GROUP BY 子句, 分组子句 + GROUP BY 字段/别名 [排序方式] + 分组后会进行排序。升序:ASC,降序:DESC + 以下[合计函数]需配合 GROUP BY 使用: + count 返回不同的非NULL值数目 count(*)、count(字段) + sum 求和 + max 求最大值 + min 求最小值 + avg 求平均值 + group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。 +e. HAVING 子句,条件子句 + 与 where 功能、用法相同,执行时机不同。 + where 在开始时执行检测数据,对原数据进行过滤。 + having 对筛选出的结果再次进行过滤。 + having 字段必须是查询出来的,where 字段必须是数据表存在的。 + where 不可以使用字段的别名,having 可以。因为执行WHERE代码时,可能尚未确定列值。 + where 不可以使用合计函数。一般需用合计函数才会用 having + SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。 +f. ORDER BY 子句,排序子句 + order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]... + 升序:ASC,降序:DESC + 支持多个字段的排序。 +g. LIMIT 子句,限制结果数量子句 + 仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合,按照记录出现的顺序,索引从0开始。 + limit 起始位置, 获取条数 + 省略第一个参数,表示从索引0开始。limit 获取条数 +h. DISTINCT, ALL 选项 + distinct 去除重复记录 + 默认为 all, 全部记录 +``` + +### UNION + +```mysql +/* UNION */ ------------------ + 将多个select查询的结果组合成一个结果集合。 + SELECT ... UNION [ALL|DISTINCT] SELECT ... + 默认 DISTINCT 方式,即所有返回的行都是唯一的 + 建议,对每个SELECT查询加上小括号包裹。 + ORDER BY 排序时,需加上 LIMIT 进行结合。 + 需要各select查询的字段数量一样。 + 每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。 +``` + +### 子查询 + +```mysql +/* 子查询 */ ------------------ + - 子查询需用括号包裹。 +-- from型 + from后要求是一个表,必须给子查询结果取个别名。 + - 简化每个查询内的条件。 + - from型需将结果生成一个临时表格,可用以原表的锁定的释放。 + - 子查询返回一个表,表型子查询。 + select * from (select * from tb where id>0) as subfrom where id>1; +-- where型 + - 子查询返回一个值,标量子查询。 + - 不需要给子查询取别名。 + - where子查询内的表,不能直接用以更新。 + select * from tb where money = (select max(money) from tb); + -- 列子查询 + 如果子查询结果返回的是一列。 + 使用 in 或 not in 完成查询 + exists 和 not exists 条件 + 如果子查询返回数据,则返回1或0。常用于判断条件。 + select column1 from t1 where exists (select * from t2); + -- 行子查询 + 查询条件是一个行。 + select * from t1 where (id, gender) in (select id, gender from t2); + 行构造符:(col1, col2, ...) 或 ROW(col1, col2, ...) + 行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。 + -- 特殊运算符 + != all() 相当于 not in + = some() 相当于 in。any 是 some 的别名 + != some() 不等同于 not in,不等于其中某一个。 + all, some 可以配合其他运算符一起使用。 +``` + +### 连接查询(join) + +```mysql +/* 连接查询(join) */ ------------------ + 将多个表的字段进行连接,可以指定连接条件。 +-- 内连接(inner join) + - 默认就是内连接,可省略inner。 + - 只有数据存在时才能发送连接。即连接结果不能出现空行。 + on 表示连接条件。其条件表达式与where类似。也可以省略条件(表示条件永远为真) + 也可用where表示连接条件。 + 还有 using, 但需字段名相同。 using(字段名) + -- 交叉连接 cross join + 即,没有条件的内连接。 + select * from tb1 cross join tb2; +-- 外连接(outer join) + - 如果数据不存在,也会出现在连接结果中。 + -- 左外连接 left join + 如果数据不存在,左表记录会出现,而右表为null填充 + -- 右外连接 right join + 如果数据不存在,右表记录会出现,而左表为null填充 +-- 自然连接(natural join) + 自动判断连接条件完成连接。 + 相当于省略了using,会自动查找相同字段名。 + natural join + natural left join + natural right join +select info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id; +``` + +### TRUNCATE + +```mysql +/* TRUNCATE */ ------------------ +TRUNCATE [TABLE] tbl_name +清空数据 +删除重建表 +区别: +1,truncate 是删除表再创建,delete 是逐条删除 +2,truncate 重置auto_increment的值。而delete不会 +3,truncate 不知道删除了几条,而delete知道。 +4,当被用于带分区的表时,truncate 会保留分区 +``` + +### 备份与还原 + +```mysql +/* 备份与还原 */ ------------------ +备份,将数据的结构与表内数据保存起来。 +利用 mysqldump 指令完成。 +-- 导出 +mysqldump [options] db_name [tables] +mysqldump [options] ---database DB1 [DB2 DB3...] +mysqldump [options] --all--database +1. 导出一张表 +  mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql) +2. 导出多张表 +  mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql) +3. 导出所有表 +  mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql) +4. 导出一个库 +  mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql) +可以-w携带WHERE条件 +-- 导入 +1. 在登录mysql的情况下: +  source 备份文件 +2. 在不登录的情况下 +  mysql -u用户名 -p密码 库名 < 备份文件 +``` + +### 视图 + +```mysql +什么是视图: + 视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。 + 视图具有表结构文件,但不存在数据文件。 + 对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。通过视图进行查询没有任何限制,通过它们进行数据修改时的限制也很少。 + 视图是存储在数据库中的查询的sql语句,它主要出于两种原因:安全原因,视图可以隐藏一些数据,如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,另一原因是可使复杂的查询易于理解和使用。 +-- 创建视图 +CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement + - 视图名必须唯一,同时不能与表重名。 + - 视图可以使用select语句查询到的列名,也可以自己指定相应的列名。 + - 可以指定视图执行的算法,通过ALGORITHM指定。 + - column_list如果存在,则数目必须等于SELECT语句检索的列数 +-- 查看结构 + SHOW CREATE VIEW view_name +-- 删除视图 + - 删除视图后,数据依然存在。 + - 可同时删除多个视图。 + DROP VIEW [IF EXISTS] view_name ... +-- 修改视图结构 + - 一般不修改视图,因为不是所有的更新视图都会映射到表上。 + ALTER VIEW view_name [(column_list)] AS select_statement +-- 视图作用 + 1. 简化业务逻辑 + 2. 对客户端隐藏真实的表结构 +-- 视图算法(ALGORITHM) + MERGE 合并 + 将视图的查询语句,与外部查询需要先合并再执行! + TEMPTABLE 临时表 + 将视图执行完毕后,形成临时表,再做外层查询! + UNDEFINED 未定义(默认),指的是MySQL自主去选择相应的算法。 +``` + +### 事务(transaction) + +```mysql +事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。 + - 支持连续SQL的集体成功或集体撤销。 + - 事务是数据库在数据晚自习方面的一个功能。 + - 需要利用 InnoDB 或 BDB 存储引擎,对自动提交的特性支持完成。 + - InnoDB被称为事务安全型引擎。 +-- 事务开启 + START TRANSACTION; 或者 BEGIN; + 开启事务后,所有被执行的SQL语句均被认作当前事务内的SQL语句。 +-- 事务提交 + COMMIT; +-- 事务回滚 + ROLLBACK; + 如果部分操作发生问题,映射到事务开启前。 +-- 事务的特性 + 1. 原子性(Atomicity) + 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 + 2. 一致性(Consistency) + 事务前后数据的完整性必须保持一致。 + - 事务开始和结束时,外部数据一致 + - 在整个事务过程中,操作是连续的 + 3. 隔离性(Isolation) + 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。 + 4. 持久性(Durability) + 一个事务一旦被提交,它对数据库中的数据改变就是永久性的。 +-- 事务的实现 + 1. 要求是事务支持的表类型 + 2. 执行一组相关的操作前开启事务 + 3. 整组操作完成后,都成功,则提交;如果存在失败,选择回滚,则会回到事务开始的备份点。 +-- 事务的原理 + 利用InnoDB的自动提交(autocommit)特性完成。 + 普通的MySQL执行语句后,当前的数据提交操作均可被其他客户端可见。 + 而事务是暂时关闭“自动提交”机制,需要commit提交持久化数据操作。 +-- 注意 + 1. 数据定义语言(DDL)语句不能被回滚,比如创建或取消数据库的语句,和创建、取消或更改表或存储的子程序的语句。 + 2. 事务不能被嵌套 +-- 保存点 + SAVEPOINT 保存点名称 -- 设置一个事务保存点 + ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点 + RELEASE SAVEPOINT 保存点名称 -- 删除保存点 +-- InnoDB自动提交特性设置 + SET autocommit = 0|1; 0表示关闭自动提交,1表示开启自动提交。 + - 如果关闭了,那普通操作的结果对其他客户端也不可见,需要commit提交后才能持久化数据操作。 + - 也可以关闭自动提交来开启事务。但与START TRANSACTION不同的是, + SET autocommit是永久改变服务器的设置,直到下次再次修改该设置。(针对当前连接) + 而START TRANSACTION记录开启前的状态,而一旦事务提交或回滚后就需要再次开启事务。(针对当前事务) + +``` + +### 锁表 + +```mysql +/* 锁表 */ +表锁定只用于防止其它客户端进行不正当地读取和写入 +MyISAM 支持表锁,InnoDB 支持行锁 +-- 锁定 + LOCK TABLES tbl_name [AS alias] +-- 解锁 + UNLOCK TABLES +``` + +### 触发器 + +```mysql +/* 触发器 */ ------------------ + 触发程序是与表有关的命名数据库对象,当该表出现特定事件时,将激活该对象 + 监听:记录的增加、修改、删除。 +-- 创建触发器 +CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt + 参数: + trigger_time是触发程序的动作时间。它可以是 before 或 after,以指明触发程序是在激活它的语句之前或之后触发。 + trigger_event指明了激活触发程序的语句的类型 + INSERT:将新行插入表时激活触发程序 + UPDATE:更改某一行时激活触发程序 + DELETE:从表中删除某一行时激活触发程序 + tbl_name:监听的表,必须是永久性的表,不能将触发程序与TEMPORARY表或视图关联起来。 + trigger_stmt:当触发程序激活时执行的语句。执行多个语句,可使用BEGIN...END复合语句结构 +-- 删除 +DROP TRIGGER [schema_name.]trigger_name +可以使用old和new代替旧的和新的数据 + 更新操作,更新前是old,更新后是new. + 删除操作,只有old. + 增加操作,只有new. +-- 注意 + 1. 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。 +-- 字符连接函数 +concat(str1,str2,...]) +concat_ws(separator,str1,str2,...) +-- 分支语句 +if 条件 then + 执行语句 +elseif 条件 then + 执行语句 +else + 执行语句 +end if; +-- 修改最外层语句结束符 +delimiter 自定义结束符号 + SQL语句 +自定义结束符号 +delimiter ; -- 修改回原来的分号 +-- 语句块包裹 +begin + 语句块 +end +-- 特殊的执行 +1. 只要添加记录,就会触发程序。 +2. Insert into on duplicate key update 语法会触发: + 如果没有重复记录,会触发 before insert, after insert; + 如果有重复记录并更新,会触发 before insert, before update, after update; + 如果有重复记录但是没有发生更新,则触发 before insert, before update +3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert +``` + +### SQL编程 + +```mysql +/* SQL编程 */ ------------------ +--// 局部变量 ---------- +-- 变量声明 + declare var_name[,...] type [default value] + 这个语句被用来声明局部变量。要给变量提供一个默认值,请包含一个default子句。值可以被指定为一个表达式,不需要为一个常数。如果没有default子句,初始值为null。 +-- 赋值 + 使用 set 和 select into 语句为变量赋值。 + - 注意:在函数内是可以使用全局变量(用户自定义的变量) +--// 全局变量 ---------- +-- 定义、赋值 +set 语句可以定义并为变量赋值。 +set @var = value; +也可以使用select into语句为变量初始化并赋值。这样要求select语句只能返回一行,但是可以是多个字段,就意味着同时为多个变量进行赋值,变量的数量需要与查询的列数一致。 +还可以把赋值语句看作一个表达式,通过select执行完成。此时为了避免=被当作关系运算符看待,使用:=代替。(set语句可以使用= 和 :=)。 +select @var:=20; +select @v1:=id, @v2=name from t1 limit 1; +select * from tbl_name where @var:=30; +select into 可以将表中查询获得的数据赋给变量。 + -| select max(height) into @max_height from tb; +-- 自定义变量名 +为了避免select语句中,用户自定义的变量与系统标识符(通常是字段名)冲突,用户自定义变量在变量名前使用@作为开始符号。 +@var=10; + - 变量被定义后,在整个会话周期都有效(登录到退出) +--// 控制结构 ---------- +-- if语句 +if search_condition then + statement_list +[elseif search_condition then + statement_list] +... +[else + statement_list] +end if; +-- case语句 +CASE value WHEN [compare-value] THEN result +[WHEN [compare-value] THEN result ...] +[ELSE result] +END +-- while循环 +[begin_label:] while search_condition do + statement_list +end while [end_label]; +- 如果需要在循环内提前终止 while循环,则需要使用标签;标签需要成对出现。 + -- 退出循环 + 退出整个循环 leave + 退出当前循环 iterate + 通过退出的标签决定退出哪个循环 +--// 内置函数 ---------- +-- 数值函数 +abs(x) -- 绝对值 abs(-10.9) = 10 +format(x, d) -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46 +ceil(x) -- 向上取整 ceil(10.1) = 11 +floor(x) -- 向下取整 floor (10.1) = 10 +round(x) -- 四舍五入去整 +mod(m, n) -- m%n m mod n 求余 10%3=1 +pi() -- 获得圆周率 +pow(m, n) -- m^n +sqrt(x) -- 算术平方根 +rand() -- 随机数 +truncate(x, d) -- 截取d位小数 +-- 时间日期函数 +now(), current_timestamp(); -- 当前日期时间 +current_date(); -- 当前日期 +current_time(); -- 当前时间 +date('yyyy-mm-dd hh:ii:ss'); -- 获取日期部分 +time('yyyy-mm-dd hh:ii:ss'); -- 获取时间部分 +date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间 +unix_timestamp(); -- 获得unix时间戳 +from_unixtime(); -- 从时间戳获得时间 +-- 字符串函数 +length(string) -- string长度,字节 +char_length(string) -- string的字符个数 +substring(str, position [,length]) -- 从str的position开始,取length个字符 +replace(str ,search_str ,replace_str) -- 在str中用replace_str替换search_str +instr(string ,substring) -- 返回substring首次在string中出现的位置 +concat(string [,...]) -- 连接字串 +charset(str) -- 返回字串字符集 +lcase(string) -- 转换成小写 +left(string, length) -- 从string2中的左边起取length个字符 +load_file(file_name) -- 从文件读取内容 +locate(substring, string [,start_position]) -- 同instr,但可指定开始位置 +lpad(string, length, pad) -- 重复用pad加在string开头,直到字串长度为length +ltrim(string) -- 去除前端空格 +repeat(string, count) -- 重复count次 +rpad(string, length, pad) --在str后用pad补充,直到长度为length +rtrim(string) -- 去除后端空格 +strcmp(string1 ,string2) -- 逐字符比较两字串大小 +-- 流程函数 +case when [condition] then result [when [condition] then result ...] [else result] end 多分支 +if(expr1,expr2,expr3) 双分支。 +-- 聚合函数 +count() +sum(); +max(); +min(); +avg(); +group_concat() +-- 其他常用函数 +md5(); +default(); +--// 存储函数,自定义函数 ---------- +-- 新建 + CREATE FUNCTION function_name (参数列表) RETURNS 返回值类型 + 函数体 + - 函数名,应该合法的标识符,并且不应该与已有的关键字冲突。 + - 一个函数应该属于某个数据库,可以使用db_name.funciton_name的形式执行当前函数所属数据库,否则为当前数据库。 + - 参数部分,由"参数名"和"参数类型"组成。多个参数用逗号隔开。 + - 函数体由多条可用的mysql语句,流程控制,变量声明等语句构成。 + - 多条语句应该使用 begin...end 语句块包含。 + - 一定要有 return 返回值语句。 +-- 删除 + DROP FUNCTION [IF EXISTS] function_name; +-- 查看 + SHOW FUNCTION STATUS LIKE 'partten' + SHOW CREATE FUNCTION function_name; +-- 修改 + ALTER FUNCTION function_name 函数选项 +--// 存储过程,自定义功能 ---------- +-- 定义 +存储存储过程 是一段代码(过程),存储在数据库中的sql组成。 +一个存储过程通常用于完成一段业务逻辑,例如报名,交班费,订单入库等。 +而一个函数通常专注与某个功能,视为其他程序服务的,需要在其他语句中调用函数才可以,而存储过程不能被其他调用,是自己执行 通过call执行。 +-- 创建 +CREATE PROCEDURE sp_name (参数列表) + 过程体 +参数列表:不同于函数的参数列表,需要指明参数类型 +IN,表示输入型 +OUT,表示输出型 +INOUT,表示混合型 +注意,没有返回值。 +``` + +### 存储过程 + +```mysql +/* 存储过程 */ ------------------ +存储过程是一段可执行性代码的集合。相比函数,更偏向于业务逻辑。 +调用:CALL 过程名 +-- 注意 +- 没有返回值。 +- 只能单独调用,不可夹杂在其他语句中 +-- 参数 +IN|OUT|INOUT 参数名 数据类型 +IN 输入:在调用过程中,将数据输入到过程体内部的参数 +OUT 输出:在调用过程中,将过程体处理完的结果返回到客户端 +INOUT 输入输出:既可输入,也可输出 +-- 语法 +CREATE PROCEDURE 过程名 (参数列表) +BEGIN + 过程体 +END +``` + +### 用户和权限管理 + +```mysql +/* 用户和权限管理 */ ------------------ +-- root密码重置 +1. 停止MySQL服务 +2. [Linux] /usr/local/mysql/bin/safe_mysqld --skip-grant-tables & + [Windows] mysqld --skip-grant-tables +3. use mysql; +4. UPDATE `user` SET PASSWORD=PASSWORD("密码") WHERE `user` = "root"; +5. FLUSH PRIVILEGES; +用户信息表:mysql.user +-- 刷新权限 +FLUSH PRIVILEGES; +-- 增加用户 +CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串) + - 必须拥有mysql数据库的全局CREATE USER权限,或拥有INSERT权限。 + - 只能创建用户,不能赋予权限。 + - 用户名,注意引号:如 'user_name'@'192.168.1.1' + - 密码也需引号,纯数字密码也要加引号 + - 要在纯文本中指定密码,需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值,需包含关键字PASSWORD +-- 重命名用户 +RENAME USER old_user TO new_user +-- 设置密码 +SET PASSWORD = PASSWORD('密码') -- 为当前用户设置密码 +SET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码 +-- 删除用户 +DROP USER 用户名 +-- 分配权限/添加用户 +GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password'] + - all privileges 表示所有权限 + - *.* 表示所有库的所有表 + - 库名.表名 表示某库下面的某表 + GRANT ALL PRIVILEGES ON `pms`.* TO 'pms'@'%' IDENTIFIED BY 'pms0817'; +-- 查看权限 +SHOW GRANTS FOR 用户名 + -- 查看当前用户权限 + SHOW GRANTS; 或 SHOW GRANTS FOR CURRENT_USER; 或 SHOW GRANTS FOR CURRENT_USER(); +-- 撤消权限 +REVOKE 权限列表 ON 表名 FROM 用户名 +REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名 -- 撤销所有权限 +-- 权限层级 +-- 要使用GRANT或REVOKE,您必须拥有GRANT OPTION权限,并且您必须用于您正在授予或撤销的权限。 +全局层级:全局权限适用于一个给定服务器中的所有数据库,mysql.user + GRANT ALL ON *.*和 REVOKE ALL ON *.*只授予和撤销全局权限。 +数据库层级:数据库权限适用于一个给定数据库中的所有目标,mysql.db, mysql.host + GRANT ALL ON db_name.*和REVOKE ALL ON db_name.*只授予和撤销数据库权限。 +表层级:表权限适用于一个给定表中的所有列,mysql.talbes_priv + GRANT ALL ON db_name.tbl_name和REVOKE ALL ON db_name.tbl_name只授予和撤销表权限。 +列层级:列权限适用于一个给定表中的单一列,mysql.columns_priv + 当使用REVOKE时,您必须指定与被授权列相同的列。 +-- 权限列表 +ALL [PRIVILEGES] -- 设置除GRANT OPTION之外的所有简单权限 +ALTER -- 允许使用ALTER TABLE +ALTER ROUTINE -- 更改或取消已存储的子程序 +CREATE -- 允许使用CREATE TABLE +CREATE ROUTINE -- 创建已存储的子程序 +CREATE TEMPORARY TABLES -- 允许使用CREATE TEMPORARY TABLE +CREATE USER -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。 +CREATE VIEW -- 允许使用CREATE VIEW +DELETE -- 允许使用DELETE +DROP -- 允许使用DROP TABLE +EXECUTE -- 允许用户运行已存储的子程序 +FILE -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE +INDEX -- 允许使用CREATE INDEX和DROP INDEX +INSERT -- 允许使用INSERT +LOCK TABLES -- 允许对您拥有SELECT权限的表使用LOCK TABLES +PROCESS -- 允许使用SHOW FULL PROCESSLIST +REFERENCES -- 未被实施 +RELOAD -- 允许使用FLUSH +REPLICATION CLIENT -- 允许用户询问从属服务器或主服务器的地址 +REPLICATION SLAVE -- 用于复制型从属服务器(从主服务器中读取二进制日志事件) +SELECT -- 允许使用SELECT +SHOW DATABASES -- 显示所有数据库 +SHOW VIEW -- 允许使用SHOW CREATE VIEW +SHUTDOWN -- 允许使用mysqladmin shutdown +SUPER -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句,mysqladmin debug命令;允许您连接(一次),即使已达到max_connections。 +UPDATE -- 允许使用UPDATE +USAGE -- “无权限”的同义词 +GRANT OPTION -- 允许授予权限 +``` + +### 表维护 + +```mysql +/* 表维护 */ +-- 分析和存储表的关键字分布 +ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ... +-- 检查一个或多个表是否有错误 +CHECK TABLE tbl_name [, tbl_name] ... [option] ... +option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED} +-- 整理数据文件的碎片 +OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... +``` + +### 杂项 + +```mysql +/* 杂项 */ ------------------ +1. 可用反引号(`)为标识符(库名、表名、字段名、索引、别名)包裹,以避免与关键字重名!中文也可以作为标识符! +2. 每个库目录存在一个保存当前数据库的选项文件db.opt。 +3. 注释: + 单行注释 # 注释内容 + 多行注释 /* 注释内容 */ + 单行注释 -- 注释内容 (标准SQL注释风格,要求双破折号后加一空格符(空格、TAB、换行等)) +4. 模式通配符: + _ 任意单个字符 + % 任意多个字符,甚至包括零字符 + 单引号需要进行转义 \' +5. CMD命令行内的语句结束符可以为 ";", "\G", "\g",仅影响显示结果。其他地方还是用分号结束。delimiter 可修改当前对话的语句结束符。 +6. SQL对大小写不敏感 +7. 清除已有语句:\c +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" new file mode 100644 index 00000000000..79b74441deb --- /dev/null +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/Leetcode-LinkList1.md" @@ -0,0 +1,421 @@ + + +- [1. 两数相加](#1-两数相加) + - [题目描述](#题目描述) + - [问题分析](#问题分析) + - [Solution](#solution) +- [2. 翻转链表](#2-翻转链表) + - [题目描述](#题目描述-1) + - [问题分析](#问题分析-1) + - [Solution](#solution-1) +- [3. 链表中倒数第k个节点](#3-链表中倒数第k个节点) + - [题目描述](#题目描述-2) + - [问题分析](#问题分析-2) + - [Solution](#solution-2) +- [4. 删除链表的倒数第N个节点](#4-删除链表的倒数第n个节点) + - [问题分析](#问题分析-3) + - [Solution](#solution-3) +- [5. 合并两个排序的链表](#5-合并两个排序的链表) + - [题目描述](#题目描述-3) + - [问题分析](#问题分析-4) + - [Solution](#solution-4) + + + + +# 1. 两数相加 + +### 题目描述 + +> Leetcode:给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。 +> +>你可以假设除了数字 0 之外,这两个数字都不会以零开头。 + +示例: + +``` +输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) +输出:7 -> 0 -> 8 +原因:342 + 465 = 807 +``` + +### 问题分析 + +Leetcode官方详细解答地址: + + https://leetcode-cn.com/problems/add-two-numbers/solution/ + +> 要对头结点进行操作时,考虑创建哑节点dummy,使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。 + +我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐 +位相加的过程。 + +![图1,对两数相加方法的可视化: 342 + 465 = 807342+465=807, 每个结点都包含一个数字,并且数字按位逆序存储。](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/34910956.jpg) + +### Solution + +**我们首先从最低有效位也就是列表 l1和 l2 的表头开始相加。注意需要考虑到进位的情况!** + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ + //https://leetcode-cn.com/problems/add-two-numbers/description/ +class Solution { +public ListNode addTwoNumbers(ListNode l1, ListNode l2) { + ListNode dummyHead = new ListNode(0); + ListNode p = l1, q = l2, curr = dummyHead; + //carry 表示进位数 + int carry = 0; + while (p != null || q != null) { + int x = (p != null) ? p.val : 0; + int y = (q != null) ? q.val : 0; + int sum = carry + x + y; + //进位数 + carry = sum / 10; + //新节点的数值为sum % 10 + curr.next = new ListNode(sum % 10); + curr = curr.next; + if (p != null) p = p.next; + if (q != null) q = q.next; + } + if (carry > 0) { + curr.next = new ListNode(carry); + } + return dummyHead.next; +} +} +``` + +# 2. 翻转链表 + + +### 题目描述 +> 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。 + +![翻转链表](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/81431871.jpg) + +### 问题分析 + +这道算法题,说直白点就是:如何让后一个节点指向前一个节点!在下面的代码中定义了一个 next 节点,该节点主要是保存要反转到头的那个节点,防止链表 “断裂”。 + +### Solution + + +```java +public class ListNode { + int val; + ListNode next = null; + + ListNode(int val) { + this.val = val; + } +} +``` + +```java +/** + * + * @author Snailclimb + * @date 2018年9月19日 + * @Description: TODO + */ +public class Solution { + + public ListNode ReverseList(ListNode head) { + + ListNode next = null; + ListNode pre = null; + + while (head != null) { + // 保存要反转到头的那个节点 + next = head.next; + // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null) + head.next = pre; + // 上一个已经反转到头部的节点 + pre = head; + // 一直向链表尾走 + head = next; + } + return pre; + } + +} +``` + +测试方法: + +```java + public static void main(String[] args) { + + ListNode a = new ListNode(1); + ListNode b = new ListNode(2); + ListNode c = new ListNode(3); + ListNode d = new ListNode(4); + ListNode e = new ListNode(5); + a.next = b; + b.next = c; + c.next = d; + d.next = e; + new Solution().ReverseList(a); + while (e != null) { + System.out.println(e.val); + e = e.next; + } + } +``` + +输出: + +``` +5 +4 +3 +2 +1 +``` + +# 3. 链表中倒数第k个节点 + +### 题目描述 + +> 剑指offer: 输入一个链表,输出该链表中倒数第k个结点。 + +### 问题分析 + +> **链表中倒数第k个节点也就是正数第(L-K+1)个节点,知道了只一点,这一题基本就没问题!** + +首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点也就是正数第(L-K+1)个节点。 + + +### Solution + +```java +/* +public class ListNode { + int val; + ListNode next = null; + + ListNode(int val) { + this.val = val; + } +}*/ + +// 时间复杂度O(n),一次遍历即可 +// https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking +public class Solution { + public ListNode FindKthToTail(ListNode head, int k) { + // 如果链表为空或者k小于等于0 + if (head == null || k <= 0) { + return null; + } + // 声明两个指向头结点的节点 + ListNode node1 = head, node2 = head; + // 记录节点的个数 + int count = 0; + // 记录k值,后面要使用 + int index = k; + // p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑, + // 当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点 + while (node1 != null) { + node1 = node1.next; + count++; + if (k < 1 && node1 != null) { + node2 = node2.next; + } + k--; + } + // 如果节点个数小于所求的倒数第k个节点,则返回空 + if (count < index) + return null; + return node2; + + } +} +``` + + +# 4. 删除链表的倒数第N个节点 + + +> Leetcode:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 + +**示例:** + +``` +给定一个链表: 1->2->3->4->5, 和 n = 2. + +当删除了倒数第二个节点后,链表变为 1->2->3->5. + +``` + +**说明:** + +给定的 n 保证是有效的。 + +**进阶:** + +你能尝试使用一趟扫描实现吗? + +该题在 leetcode 上有详细解答,具体可参考 Leetcode. + +### 问题分析 + + +我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。 + +![图 1. 删除列表中的第 L - n + 1 个元素](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/94354387.jpg) + +### Solution + +**两次遍历法** + +首先我们将添加一个 **哑结点** 作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n) 个结点那里。**我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。** + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +// https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/ +public class Solution { + public ListNode removeNthFromEnd(ListNode head, int n) { + // 哑结点,哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部 + ListNode dummy = new ListNode(0); + // 哑结点指向头结点 + dummy.next = head; + // 保存链表长度 + int length = 0; + ListNode len = head; + while (len != null) { + length++; + len = len.next; + } + length = length - n; + ListNode target = dummy; + // 找到 L-n 位置的节点 + while (length > 0) { + target = target.next; + length--; + } + // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点 + target.next = target.next.next; + return dummy.next; + } +} +``` + +**复杂度分析:** + +- **时间复杂度 O(L)** :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。 +- **空间复杂度 O(1)** :我们只用了常量级的额外空间。 + + + +**进阶——一次遍历法:** + + +> **链表中倒数第N个节点也就是正数第(L-N+1)个节点。 + +其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点) + +```java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { val = x; } + * } + */ +public class Solution { + public ListNode removeNthFromEnd(ListNode head, int n) { + + ListNode dummy = new ListNode(0); + dummy.next = head; + // 声明两个指向头结点的节点 + ListNode node1 = dummy, node2 = dummy; + + // node1 节点先跑,node1节点 跑到第 n 个节点的时候,node2 节点开始跑 + // 当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点,也就是倒数第 n+1(L代表总链表长度) + while (node1 != null) { + node1 = node1.next; + if (n < 1 && node1 != null) { + node2 = node2.next; + } + n--; + } + + node2.next = node2.next.next; + + return dummy.next; + + } +} +``` + + + + + +# 5. 合并两个排序的链表 + +### 题目描述 + +> 剑指offer:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。 + +### 问题分析 + +我们可以这样分析: + +1. 假设我们有两个链表 A,B; +2. A的头节点A1的值与B的头结点B1的值比较,假设A1小,则A1为头节点; +3. A2再和B1比较,假设B1小,则,A1指向B1; +4. A2再和B2比较 +就这样循环往复就行了,应该还算好理解。 + +考虑通过递归的方式实现! + +### Solution + +**递归版本:** + +```java +/* +public class ListNode { + int val; + ListNode next = null; + + ListNode(int val) { + this.val = val; + } +}*/ +//https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking +public class Solution { +public ListNode Merge(ListNode list1,ListNode list2) { + if(list1 == null){ + return list2; + } + if(list2 == null){ + return list1; + } + if(list1.val <= list2.val){ + list1.next = Merge(list1.next, list2); + return list1; + }else{ + list2.next = Merge(list1, list2.next); + return list2; + } + } +} +``` + diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" index 79a03d391d8..8dddb1b9f4b 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\270\270\350\247\201\345\256\211\345\205\250\347\256\227\346\263\225\357\274\210MD5\343\200\201SHA1\343\200\201Base64\347\255\211\347\255\211\357\274\211\346\200\273\347\273\223.md" @@ -499,7 +499,7 @@ public class DesDemo { - **这种算法是在DES算法的基础上发展出来的,类似于三重DES。** - **发展IDEA也是因为感到DES具有密钥太短等缺点。** -- **DEA的密钥为128位,这么长的密钥在今后若干年内应该是安全的。** +- **IDEA的密钥为128位,这么长的密钥在今后若干年内应该是安全的。** - **在实际项目中用到的很少了解即可。** #### 代码实现: diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" index a034e88e95e..938f4a48f1a 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\220\236\345\256\232BAT\351\235\242\350\257\225\342\200\224\342\200\224\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" @@ -1,3 +1,20 @@ + + +- [说明](#说明) +- [1. KMP 算法](#1-kmp-算法) +- [2. 替换空格](#2-替换空格) +- [3. 最长公共前缀](#3-最长公共前缀) +- [4. 回文串](#4-回文串) + - [4.1. 最长回文串](#41-最长回文串) + - [4.2. 验证回文串](#42-验证回文串) + - [4.3. 最长回文子串](#43-最长回文子串) + - [4.4. 最长回文子序列](#44-最长回文子序列) +- [5. 括号匹配深度](#5-括号匹配深度) +- [6. 把字符串转换成整数](#6-把字符串转换成整数) + + + + ## 说明 - 本文作者:wwwxmu @@ -36,34 +53,34 @@ //https://www.weiweiblog.cn/replacespace/ public class Solution { - /** - * 第一种方法:常规方法。利用String.charAt(i)以及String.valueOf(char).equals(" " - * )遍历字符串并判断元素是否为空格。是则替换为"%20",否则不替换 - */ - public static String replaceSpace(StringBuffer str) { - - int length = str.length(); - // System.out.println("length=" + length); - StringBuffer result = new StringBuffer(); - for (int i = 0; i < length; i++) { - char b = str.charAt(i); - if (String.valueOf(b).equals(" ")) { - result.append("%20"); - } else { - result.append(b); - } - } - return result.toString(); + /** + * 第一种方法:常规方法。利用String.charAt(i)以及String.valueOf(char).equals(" " + * )遍历字符串并判断元素是否为空格。是则替换为"%20",否则不替换 + */ + public static String replaceSpace(StringBuffer str) { + + int length = str.length(); + // System.out.println("length=" + length); + StringBuffer result = new StringBuffer(); + for (int i = 0; i < length; i++) { + char b = str.charAt(i); + if (String.valueOf(b).equals(" ")) { + result.append("%20"); + } else { + result.append(b); + } + } + return result.toString(); - } + } - /** - * 第二种方法:利用API替换掉所用空格,一行代码解决问题 - */ - public static String replaceSpace2(StringBuffer str) { + /** + * 第二种方法:利用API替换掉所用空格,一行代码解决问题 + */ + public static String replaceSpace2(StringBuffer str) { - return str.toString().replaceAll("\\s", "%20"); - } + return str.toString().replaceAll("\\s", "%20"); + } } ``` @@ -91,18 +108,17 @@ public class Solution { 思路很简单!先利用Arrays.sort(strs)为数组排序,再将数组第一个元素和最后一个元素的字符从前往后对比即可! ```java -//https://leetcode-cn.com/problems/longest-common-prefix/description/ public class Main { public static String replaceSpace(String[] strs) { + // 如果检查值不合法及就返回空串 + if (!chechStrs(strs)) { + return ""; + } // 数组长度 int len = strs.length; // 用于保存结果 - StringBuffer res = new StringBuffer(); - // 注意:=是赋值,==是判断 - if (strs == null || strs.length == 0) { - return ""; - } + StringBuilder res = new StringBuilder(); // 给字符串数组的元素按照升序排序(包含数字的话,数字会排在前面) Arrays.sort(strs); int m = strs[0].length(); @@ -117,11 +133,31 @@ public class Main { } return res.toString(); - } - //测试 + } + + private static boolean chechStrs(String[] strs) { + boolean flag = false; + // 注意:=是赋值,==是判断 + if (strs != null) { + // 遍历strs检查元素值 + for (int i = 0; i < strs.length; i++) { + if (strs[i] != null && strs[i].length() != 0) { + flag = true; + } else { + flag = false; + } + } + } + return flag; + } + + // 测试 public static void main(String[] args) { String[] strs = { "customer", "car", "cat" }; - System.out.println(Main.replaceSpace(strs));//c + // String[] strs = { "customer", "car", null };//空串 + // String[] strs = {};//空串 + // String[] strs = null;//空串 + System.out.println(Main.replaceSpace(strs));// c } } @@ -161,23 +197,23 @@ public class Main { ```java //https://leetcode-cn.com/problems/longest-palindrome/description/ class Solution { - public int longestPalindrome(String s) { - if (s.length() == 0) - return 0; - // 用于存放字符 - HashSet hashset = new HashSet(); - char[] chars = s.toCharArray(); - int count = 0; - for (int i = 0; i < chars.length; i++) { - if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去 - hashset.add(chars[i]); - } else {// 如果有,就让count++(说明找到了一个成对的字符),然后把该字符移除 - hashset.remove(chars[i]); - count++; - } - } - return hashset.isEmpty() ? count * 2 : count * 2 + 1; - } + public int longestPalindrome(String s) { + if (s.length() == 0) + return 0; + // 用于存放字符 + HashSet hashset = new HashSet(); + char[] chars = s.toCharArray(); + int count = 0; + for (int i = 0; i < chars.length; i++) { + if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去 + hashset.add(chars[i]); + } else {// 如果有,就让count++(说明找到了一个成对的字符),然后把该字符移除 + hashset.remove(chars[i]); + count++; + } + } + return hashset.isEmpty() ? count * 2 : count * 2 + 1; + } } ``` @@ -203,26 +239,26 @@ class Solution { ```java //https://leetcode-cn.com/problems/valid-palindrome/description/ class Solution { - public boolean isPalindrome(String s) { - if (s.length() == 0) - return true; - int l = 0, r = s.length() - 1; - while (l < r) { - // 从头和尾开始向中间遍历 - if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况 - l++; - } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况 - r--; - } else { - // 判断二者是否相等 - if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) - return false; - l++; - r--; - } - } - return true; - } + public boolean isPalindrome(String s) { + if (s.length() == 0) + return true; + int l = 0, r = s.length() - 1; + while (l < r) { + // 从头和尾开始向中间遍历 + if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况 + l++; + } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况 + r--; + } else { + // 判断二者是否相等 + if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r))) + return false; + l++; + r--; + } + } + return true; + } } ``` @@ -254,28 +290,28 @@ class Solution { ```java //https://leetcode-cn.com/problems/longest-palindromic-substring/description/ class Solution { - private int index, len; - - public String longestPalindrome(String s) { - if (s.length() < 2) - return s; - for (int i = 0; i < s.length() - 1; i++) { - PalindromeHelper(s, i, i); - PalindromeHelper(s, i, i + 1); - } - return s.substring(index, index + len); - } + private int index, len; + + public String longestPalindrome(String s) { + if (s.length() < 2) + return s; + for (int i = 0; i < s.length() - 1; i++) { + PalindromeHelper(s, i, i); + PalindromeHelper(s, i, i + 1); + } + return s.substring(index, index + len); + } - public void PalindromeHelper(String s, int l, int r) { - while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { - l--; - r++; - } - if (len < r - l - 1) { - index = l + 1; - len = r - l - 1; - } - } + public void PalindromeHelper(String s, int l, int r) { + while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { + l--; + r++; + } + if (len < r - l - 1) { + index = l + 1; + len = r - l - 1; + } + } } ``` @@ -329,7 +365,7 @@ class Solution { } ``` -### 5. 括号匹配深度 +## 5. 括号匹配深度 > 爱奇艺 2018 秋招 Java: >一个合法的括号匹配序列有以下定义: @@ -381,20 +417,20 @@ import java.util.Scanner; * @Description: TODO 求给定合法括号序列的深度 */ public class Main { - public static void main(String[] args) { - Scanner sc = new Scanner(System.in); - String s = sc.nextLine(); - int cnt = 0, max = 0, i; - for (i = 0; i < s.length(); ++i) { - if (s.charAt(i) == '(') - cnt++; - else - cnt--; - max = Math.max(max, cnt); - } - sc.close(); - System.out.println(max); - } + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + String s = sc.nextLine(); + int cnt = 0, max = 0, i; + for (i = 0; i < s.length(); ++i) { + if (s.charAt(i) == '(') + cnt++; + else + cnt--; + max = Math.max(max, cnt); + } + sc.close(); + System.out.println(max); + } } ``` @@ -407,39 +443,39 @@ public class Main { //https://www.weiweiblog.cn/strtoint/ public class Main { - public static int StrToInt(String str) { - if (str.length() == 0) - return 0; - char[] chars = str.toCharArray(); - // 判断是否存在符号位 - int flag = 0; - if (chars[0] == '+') - flag = 1; - else if (chars[0] == '-') - flag = 2; - int start = flag > 0 ? 1 : 0; - int res = 0;// 保存结果 - for (int i = start; i < chars.length; i++) { - if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False - int temp = chars[i] - '0'; - res = res * 10 + temp; - } else { - return 0; - } - } - return flag == 1 ? res : -res; + public static int StrToInt(String str) { + if (str.length() == 0) + return 0; + char[] chars = str.toCharArray(); + // 判断是否存在符号位 + int flag = 0; + if (chars[0] == '+') + flag = 1; + else if (chars[0] == '-') + flag = 2; + int start = flag > 0 ? 1 : 0; + int res = 0;// 保存结果 + for (int i = start; i < chars.length; i++) { + if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False + int temp = chars[i] - '0'; + res = res * 10 + temp; + } else { + return 0; + } + } + return flag == 1 ? res : -res; - } + } - public static void main(String[] args) { - // TODO Auto-generated method stub - String s = "-12312312"; - System.out.println("使用库函数转换:" + Integer.valueOf(s)); - int res = Main.StrToInt(s); - System.out.println("使用自己写的方法转换:" + res); + public static void main(String[] args) { + // TODO Auto-generated method stub + String s = "-12312312"; + System.out.println("使用库函数转换:" + Integer.valueOf(s)); + int res = Main.StrToInt(s); + System.out.println("使用自己写的方法转换:" + res); - } + } } -``` \ No newline at end of file +``` diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" index 8b8207f7f82..1633da7412e 100644 --- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" +++ "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -81,8 +81,8 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 **Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈(Stack)设计与实现](https://blog.csdn.net/javazejian/article/details/53362993) ### ArrayList 和 LinkedList 源码学习 -- [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/ArrayList.md) -- [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java相关/LinkedList.md) +- [ArrayList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java/ArrayList.md) +- [LinkedList 源码学习](https://github.com/Snailclimb/Java-Guide/blob/master/Java/LinkedList.md) ### 推荐阅读 @@ -112,7 +112,7 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树 * ### 3 满二叉树 - + [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同) 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。 @@ -145,13 +145,13 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度) - 红黑树的应用: - + TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。 - 为什么要用红黑树 - + 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - + - 推荐文章: - [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐) - [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错) @@ -163,20 +163,28 @@ Set 继承于 Collection 接口,是一个不允许出现重复元素,并且 [《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186) [《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405) - + B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance) 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。 - 3. B*树 是B+树的变体,B*树分配新结点的概率比B+树要低,空间使用率更高; + 3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高; * ### 8 LSM 树 [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599) - + B+树最大的性能问题是会产生大量的随机IO 为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。 - + [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html) +## 图 + + + + +## BFS及DFS + +- [《使用BFS及DFS遍历树和图的思路及实现》](https://blog.csdn.net/Gene1994/article/details/85097507) diff --git "a/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" "b/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" new file mode 100644 index 00000000000..f975930f7ee --- /dev/null +++ "b/\346\236\266\346\236\204/8 \345\274\240\345\233\276\350\257\273\346\207\202\345\244\247\345\236\213\347\275\221\347\253\231\346\212\200\346\234\257\346\236\266\346\236\204.md" @@ -0,0 +1,47 @@ +> 本文是作者读 《大型网站技术架构》所做的思维导图,在这里分享给各位,公众号(JavaGuide)后台回复:“架构”。即可获得下面图片的源文件以及思维导图源文件! + + + +- [1. 大型网站架构演化](#1-大型网站架构演化) +- [2. 大型架构模式](#2-大型架构模式) +- [3. 大型网站核心架构要素](#3-大型网站核心架构要素) +- [4. 瞬时响应:网站的高性能架构](#4-瞬时响应网站的高性能架构) +- [5. 万无一失:网站的高可用架构](#5-万无一失网站的高可用架构) +- [6. 永无止境:网站的伸缩性架构](#6-永无止境网站的伸缩性架构) +- [7. 随机应变:网站的可扩展性架构](#7-随机应变网站的可扩展性架构) +- [8. 固若金汤:网站的安全机构](#8-固若金汤网站的安全机构) + + + + +### 1. 大型网站架构演化 + +![1. 大型网站架构演化](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/1%20%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%9E%B6%E6%9E%84%E6%BC%94%E5%8C%96.png) + +### 2. 大型架构模式 + +![2. 大型架构模式](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2%20%E5%A4%A7%E5%9E%8B%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F.png) + +### 3. 大型网站核心架构要素 + +![3. 大型网站核心架构要素](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/3%20%E5%A4%A7%E5%9E%8B%E7%BD%91%E7%AB%99%E6%A0%B8%E5%BF%83%E6%9E%B6%E6%9E%84%E8%A6%81%E7%B4%A0.png) + +### 4. 瞬时响应:网站的高性能架构 + +![4. 瞬时响应:网站的高性能架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/4%20%E7%9E%AC%E6%97%B6%E5%93%8D%E5%BA%94%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E9%AB%98%E6%80%A7%E8%83%BD%E6%9E%B6%E6%9E%84.png) + +### 5. 万无一失:网站的高可用架构 + +![5. 万无一失:网站的高可用架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/5%20%E4%B8%87%E6%97%A0%E4%B8%80%E5%A4%B1%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E9%AB%98%E5%8F%AF%E7%94%A8%E6%9E%B6%E6%9E%84.png) + +### 6. 永无止境:网站的伸缩性架构 + +![6. 永无止境:网站的伸缩性架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/6%20%E6%B0%B8%E6%97%A0%E6%AD%A2%E5%A2%83%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E4%BC%B8%E7%BC%A9%E6%80%A7%E6%9E%B6%E6%9E%84.png) + +### 7. 随机应变:网站的可扩展性架构 + +![7. 随机应变:网站的可扩展性架构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/7%20%E9%9A%8F%E6%9C%BA%E5%BA%94%E5%8F%98%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E5%8F%AF%E6%89%A9%E5%B1%95%E6%9E%B6%E6%9E%84.png) + +### 8. 固若金汤:网站的安全机构 + +![enter image description here](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/8%20%E5%9B%BA%E8%8B%A5%E9%87%91%E6%B1%A4%EF%BC%9A%E7%BD%91%E7%AB%99%E7%9A%84%E5%AE%89%E5%85%A8%E6%9E%B6%E6%9E%84.png) diff --git "a/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" "b/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..47ba541fb4d --- /dev/null +++ "b/\346\236\266\346\236\204/\343\200\220\351\235\242\350\257\225\347\262\276\351\200\211\343\200\221\345\205\263\344\272\216\345\244\247\345\236\213\347\275\221\347\253\231\347\263\273\347\273\237\346\236\266\346\236\204\344\275\240\344\270\215\345\276\227\344\270\215\346\207\202\347\232\20410\344\270\252\351\227\256\351\242\230.md" @@ -0,0 +1,190 @@ +下面这些问题都是一线大厂的真实面试问题,不论是对你面试还是说拓宽知识面都很有帮助。之前发过一篇[8 张图读懂大型网站技术架构](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484416&idx=1&sn=6ced00d65491ef8fd33151bdfa8895c9&chksm=fd985261caefdb779412974a6a7207c93d0c2da5b28489afb74acd2fee28505daebbadb018ff&token=177958022&lang=zh_CN#rd) 可以作为不太了解大型网站系统技术架构朋友的入门文章。 + + + +- [1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量](#1-你使用过哪些组件或者方法来提升网站性能可用性以及并发量) +- [2. 设计高可用系统的常用手段](#2-设计高可用系统的常用手段) +- [3. 现代互联网应用系统通常具有哪些特点?](#3-现代互联网应用系统通常具有哪些特点) +- [4. 谈谈你对微服务领域的了解和认识](#4-谈谈你对微服务领域的了解和认识) +- [5. 谈谈你对 Dubbo 和 Spring Cloud 的认识\(两者关系\)](#5-谈谈你对-dubbo-和-spring-cloud-的认识两者关系) +- [6. 性能测试了解吗?说说你知道的性能测试工具?](#6-性能测试了解吗说说你知道的性能测试工具) +- [7. 对于一个单体应用系统,随着产品使用的用户越来越多,网站的流量会增加,最终单台服务器无法处理那么大的流量怎么办?](#7-对于一个单体应用系统随着产品使用的用户越来越多网站的流量会增加最终单台服务器无法处理那么大的流量怎么办) +- [8. 大表优化的常见手段](#8-大表优化的常见手段) +- [9. 在系统中使用消息队列能带来什么好处?](#9-在系统中使用消息队列能带来什么好处) + - [1) 通过异步处理提高系统性能](#1-通过异步处理提高系统性能) +- [2) 降低系统耦合性](#2-降低系统耦合性) +- [10. 说说自己对 CAP 定理,BASE 理论的了解](#10-说说自己对-cap-定理base-理论的了解) + - [CAP 定理](#cap-定理) + - [BASE 理论](#base-理论) +- [参考](#参考) + + + + +### 1. 你使用过哪些组件或者方法来提升网站性能,可用性以及并发量 + +1. **提高硬件能力、增加系统服务器**。(当服务器增加到某个程度的时候系统所能提供的并发访问量几乎不变,所以不能根本解决问题) +2. **使用缓存**(本地缓存:本地可以使用JDK自带的 Map、Guava Cache.分布式缓存:Redis、Memcache.本地缓存不适用于提高系统并发量,一般是用处用在程序中。比如Spring是如何实现单例的呢?大家如果看过源码的话,应该知道,S把已经初始过的变量放在一个Map中,下次再要使用这个变量的时候,先判断Map中有没有,这也就是系统中常见的单例模式的实现。) +3. **消息队列** (解耦+削峰+异步) +4. **采用分布式开发** (不同的服务部署在不同的机器节点上,并且一个服务也可以部署在多台机器上,然后利用 Nginx 负载均衡访问。这样就解决了单点部署(All In)的缺点,大大提高的系统并发量) +5. **数据库分库(读写分离)、分表(水平分表、垂直分表)** +6. **采用集群** (多台机器提供相同的服务) +7. **CDN 加速** (将一些静态资源比如图片、视频等等缓存到离用户最近的网络节点) +8. **浏览器缓存** +9. **使用合适的连接池**(数据库连接池、线程池等等) +10. **适当使用多线程进行开发。** + + +### 2. 设计高可用系统的常用手段 + +1. **降级:** 服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。降级往往会指定不同的级别,面临不同的异常等级执行不同的处理。根据服务方式:可以拒接服务,可以延迟服务,也有时候可以随机服务。根据服务范围:可以砍掉某个功能,也可以砍掉某些模块。总之服务降级需要根据不同的业务需求采用不同的降级策略。主要的目的就是服务虽然有损但是总比没有好; +2. **限流:** 防止恶意请求流量、恶意攻击,或者防止流量超出系统峰值; +3. **缓存:** 避免大量请求直接落到数据库,将数据库击垮; +4. **超时和重试机制:** 避免请求堆积造成雪崩; +5. **回滚机制:** 快速修复错误版本。 + + +### 3. 现代互联网应用系统通常具有哪些特点? + +1. 高并发,大流量; +2. 高可用:系统7×24小时不间断服务; +3. 海量数据:需要存储、管理海量数据,需要使用大量服务器; +4. 用户分布广泛,网络情况复杂:许多大型互联网都是为全球用户提供服务的,用户分布范围广,各地网络情况千差万别; +5. 安全环境恶劣:由于互联网的开放性,使得互联网更容易受到攻击,大型网站几乎每天都会被黑客攻击; +6. 需求快速变更,发布频繁:和传统软件的版本发布频率不同,互联网产品为快速适应市场,满足用户需求,其产品发布频率是极高的; +7. 渐进式发展:与传统软件产品或企业应用系统一开始就规划好全部的功能和非功能需求不同,几乎所有的大型互联网网站都是从一个小网站开始,渐进地发展起来。 + +### 4. 谈谈你对微服务领域的了解和认识 + +现在大公司都在用并且未来的趋势都是 Spring Cloud,而阿里开源的 Spring Cloud Alibaba 也是 Spring Cloud 规范的实现 。 + +我们通常把 Spring Cloud 理解为一系列开源组件的集合,但是 Spring Cloud并不是等同于 Spring Cloud Netflix 的 Ribbon、Feign、Eureka(停止更新)、Hystrix 这一套组件,而是抽象了一套通用的开发模式。它的目的是通过抽象出这套通用的模式,让开发者更快更好地开发业务。但是这套开发模式运行时的实际载体,还是依赖于 RPC、网关、服务发现、配置管理、限流熔断、分布式链路跟踪等组件的具体实现。 + +Spring Cloud Alibaba 是官方认证的新一套 Spring Cloud 规范的实现,Spring Cloud Alibaba 是一套国产开源产品集合,后续还会有中文 reference 和一些原理分析文章,所以,这对于国内的开发者是非常棒的一件事。阿里的这一举动势必会推动国内微服务技术的发展,因为在没有 Spring Cloud Alibaba 之前,我们的第一选择是 Spring Cloud Netflix,但是它们的文档都是英文的,出问题后排查也比较困难, 在国内并不是有特别多的人精通。Spring Cloud Alibaba 由阿里开源组件和阿里云产品组件两部分组成,其致力于提供微服务一站式解决方案,方便开发者通过 Spring Cloud 编程模型轻松开发微服务应用。 + +另外,Apache Dubbo Ecosystem 是围绕 Apache Dubbo 打造的微服务生态,是经过生产验证的微服务的最佳实践组合。在阿里巴巴的微服务解决方案中,Dubbo、Nacos 和 Sentinel,以及后续将开源的微服务组件,都是 Dubbo EcoSystem 的一部分。阿里后续也会将 Dubbo EcoSystem 集成到 Spring Cloud 的生态中。 + +### 5. 谈谈你对 Dubbo 和 Spring Cloud 的认识(两者关系) + +具体可以看公众号-阿里巴巴中间件的这篇文章:[独家解读:Dubbo Ecosystem - 从微服务框架到微服务生态](https://mp.weixin.qq.com/s/iNVctXw7tUGHhnF0hV84ww) + +Dubbo 与 Spring Cloud 并不是竞争关系,Dubbo 作为成熟的 RPC 框架,其易用性、扩展性和健壮性已得到业界的认可。未来 Dubbo 将会作为 Spring Cloud Alibaba 的 RPC 组件,并与 Spring Cloud 原生的 Feign 以及 RestTemplate 进行无缝整合,实现“零”成本迁移。 + +在阿里巴巴的微服务解决方案中,Dubbo、Nacos 和 Sentinel,以及后续将开源的微服务组件,都是 Dubbo EcoSystem 的一部分。我们后续也会将 Dubbo EcoSystem 集成到 Spring Cloud 的生态中。 + +### 6. 性能测试了解吗?说说你知道的性能测试工具? + +性能测试指通过自动化的测试工具模拟多种正常、峰值以及异常负载条件来对系统的各项性能指标进行测试。性能测试是总称,通常细分为: + +1. **基准测试:** 在给系统施加较低压力时,查看系统的运行状况并记录相关数做为基础参考 +2. **负载测试:**是指对系统不断地增加压力或增加一定压力下的持续时间,直到系统的某项或多项性能指标达到安全临界值,例如某种资源已经达到饱和状态等 。此时继续加压,系统处理能力会下降。 +3. **压力测试:** 超过安全负载情况下,不断施加压力(增加并发请求),直到系统崩溃或无法处理任何请求,依此获得系统最大压力承受能力。 +4. **稳定性测试:** 被测试系统在特定硬件、软件、网络环境下,加载一定业务压力(模拟生产环境不同时间点、不均匀请求,呈波浪特性)运行一段较长时间,以此检测系统是否稳定。 + +后端程序员或者测试平常比较常用的测试工具是 JMeter(官网:[https://jmeter.apache.org/](https://jmeter.apache.org/))。Apache JMeter 是一款基于Java的压力测试工具(100%纯Java应用程序),旨在加载测试功能行为和测量性能。它最初被设计用于 Web 应用测试但后来扩展到其他测试领域。 + +### 7. 对于一个单体应用系统,随着产品使用的用户越来越多,网站的流量会增加,最终单台服务器无法处理那么大的流量怎么办? + +这个时候就要考虑扩容了。《亿级流量网站架构核心技术》这本书上面介绍到我们可以考虑下面几步来解决这个问题: + +- 第一步,可以考虑简单的扩容来解决问题。比如增加系统的服务器,提高硬件能力等等。 +- 第二步,如果简单扩容搞不定,就需要水平拆分和垂直拆分数据/应用来提升系统的伸缩性,即通过扩容提升系统负载能力。 +- 第三步,如果通过水平拆分/垂直拆分还是搞不定,那就需要根据现有系统特性,架构层面进行重构甚至是重新设计,即推倒重来。 + +对于系统设计,理想的情况下应支持线性扩容和弹性扩容,即在系统瓶颈时,只需要增加机器就可以解决系统瓶颈,如降低延迟提升吞吐量,从而实现扩容需求。 + +如果你想扩容,则支持水平/垂直伸缩是前提。在进行拆分时,一定要清楚知道自己的目的是什么,拆分后带来的问题如何解决,拆分后如果没有得到任何收益就不要为了 +拆而拆,即不要过度拆分,要适合自己的业务。 + +### 8. 大表优化的常见手段 + + 当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下: + +1. **限定数据的范围:** 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。; +2. **读/写分离:** 经典的数据库拆分方案,主库负责写,从库负责读; +3. **垂直分区:** **根据数据库里面数据表的相关性进行拆分。** 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。**简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。** 如下图所示,这样来说大家应该就更容易理解了。![](https://user-gold-cdn.xitu.io/2018/6/16/164084354ba2e0fd?w=950&h=279&f=jpeg&s=26015)**垂直拆分的优点:** 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。**垂直拆分的缺点:** 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂; +4. **水平分区:** **保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。** 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。![数据库水平拆分](https://user-gold-cdn.xitu.io/2018/6/16/164084b7e9e423e3?w=690&h=271&f=jpeg&s=23119)水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 **水平拆分最好分库** 。水平拆分能够 **支持非常大的数据量存储,应用端改造也少**,但 **分片事务难以解决** ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 **尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度** ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。 + +**下面补充一下数据库分片的两种常见方案:** + +- **客户端代理:** **分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。** 当当网的 **Sharding-JDBC** 、阿里的TDDL是两种比较常用的实现。 +- **中间件代理:** **在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。** 我们现在谈的 **Mycat** 、360的Atlas、网易的DDB等等都是这种架构的实现。 + +### 9. 在系统中使用消息队列能带来什么好处? + +**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。** + +#### 1) 通过异步处理提高系统性能 +![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123) +如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。** + +通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: +![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550) +因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。 + +### 2) 降低系统耦合性 +我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和2.**分布式服务**。 + +> **先来简单说一下分布式服务:** + +目前使用比较多的用来构建**SOA(Service Oriented Architecture面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**.如果想深入了解Dubbo的可以看我写的关于Dubbo的这一篇文章:**《高性能优秀的服务框架-dubbo介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c) + +> **再来谈我们的分布式消息队列:** + +我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 + +我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: +![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946) +**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。 + +消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 + +**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。** + +**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的ActiveMQ消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。 + +> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题! + +### 10. 说说自己对 CAP 定理,BASE 理论的了解 + +#### CAP 定理 + +![CAP定理](https://user-gold-cdn.xitu.io/2018/5/24/163912e973ecb93c?w=624&h=471&f=png&s=32984) +在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点: + +- **一致性(Consistence)** :所有节点访问同一份最新的数据副本 +- **可用性(Availability)**:每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据 +- **分区容错性(Partition tolerance)** : 分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。 + +CAP仅适用于原子读写的NOSQL场景中,并不适合数据库系统。现在的分布式系统具有更多特性比如扩展性、可用性等等,在进行系统设计和开发时,我们不应该仅仅局限在CAP问题上。 + +**注意:不是所谓的3选2(不要被网上大多数文章误导了):** + +大部分人解释这一定律时,常常简单的表述为:“一致性、可用性、分区容忍性三者你只能同时达到其中两个,不可能同时达到”。实际上这是一个非常具有误导性质的说法,而且在CAP理论诞生12年之后,CAP之父也在2012年重写了之前的论文。 + +**当发生网络分区的时候,如果我们要继续服务,那么强一致性和可用性只能2选1。也就是说当网络分区之后P是前提,决定了P之后才有C和A的选择。也就是说分区容错性(Partition tolerance)我们是必须要实现的。** + +我在网上找了很多文章想看一下有没有文章提到这个不是所谓的3选2,用百度半天没找到了一篇,用谷歌搜索找到一篇比较不错的,如果想深入学习一下CAP就看这篇文章把,我这里就不多BB了:**《分布式系统之CAP理论》 :** [http://www.cnblogs.com/hxsyl/p/4381980.html](http://www.cnblogs.com/hxsyl/p/4381980.html) + + +#### BASE 理论 + +**BASE** 是 **Basically Available(基本可用)** 、**Soft-state(软状态)** 和 **Eventually Consistent(最终一致性)** 三个短语的缩写。BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定理逐步演化而来的,它大大降低了我们对系统的要求。 + +**BASE理论的核心思想:** 即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。也就是牺牲数据的一致性来满足系统的高可用性,系统中一部分数据不可用或者不一致时,仍需要保持系统整体“主要可用”。 + + +**BASE理论三要素:** + +![BASE理论三要素](https://user-gold-cdn.xitu.io/2018/5/24/163914806d9e15c6?w=612&h=461&f=png&s=39129) + +1. **基本可用:** 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但是,这绝不等价于系统不可用。 比如: **①响应时间上的损失**:正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒;**②系统功能上的损失**:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面; +2. **软状态:** 软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时; +3. **最终一致性:** 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。 + +### 参考 + +- 《大型网站技术架构》 +- 《亿级流量网站架构核心技术》 +- 《Java工程师修炼之道》 +- https://www.cnblogs.com/puresoul/p/5456855.html diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" new file mode 100644 index 00000000000..4132144608c --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/HTTPS\344\270\255\347\232\204TLS.md" @@ -0,0 +1,137 @@ + + +- [1. SSL 与 TLS](#1-ssl-%E4%B8%8E-tls) +- [2. 从网络协议的角度理解 HTTPS](#2-%E4%BB%8E%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE%E7%9A%84%E8%A7%92%E5%BA%A6%E7%90%86%E8%A7%A3-https) +- [3. 从密码学的角度理解 HTTPS](#3-%E4%BB%8E%E5%AF%86%E7%A0%81%E5%AD%A6%E7%9A%84%E8%A7%92%E5%BA%A6%E7%90%86%E8%A7%A3-https) + - [3.1. TLS 工作流程](#31-tls-%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B) + - [3.2. 密码基础](#32-%E5%AF%86%E7%A0%81%E5%9F%BA%E7%A1%80) + - [3.2.1. 伪随机数生成器](#321-%E4%BC%AA%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90%E5%99%A8) + - [3.2.2. 消息认证码](#322-%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81) + - [3.2.3. 数字签名](#323-%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D) + - [3.2.4. 公钥密码](#324-%E5%85%AC%E9%92%A5%E5%AF%86%E7%A0%81) + - [3.2.5. 证书](#325-%E8%AF%81%E4%B9%A6) + - [3.2.6. 密码小结](#326-%E5%AF%86%E7%A0%81%E5%B0%8F%E7%BB%93) + - [3.3. TLS 使用的密码技术](#33-tls-%E4%BD%BF%E7%94%A8%E7%9A%84%E5%AF%86%E7%A0%81%E6%8A%80%E6%9C%AF) + - [3.4. TLS 总结](#34-tls-%E6%80%BB%E7%BB%93) +- [4. RSA 简单示例](#4-rsa-%E7%AE%80%E5%8D%95%E7%A4%BA%E4%BE%8B) +- [5. 参考](#5-%E5%8F%82%E8%80%83) + + + +# 1. SSL 与 TLS + +SSL:(Secure Socket Layer) 安全套接层,于 1994 年由网景公司设计,并于 1995 年发布了 3.0 版本 +TLS:(Transport Layer Security)传输层安全性协议,是 IETF 在 SSL3.0 的基础上设计的协议 +以下全部使用 TLS 来表示 + +# 2. 从网络协议的角度理解 HTTPS + +![此图并不准确][1] +HTTP:HyperText Transfer Protocol 超文本传输协议 +HTTPS:Hypertext Transfer Protocol Secure 超文本传输安全协议 +TLS:位于 HTTP 和 TCP 之间的协议,其内部有 TLS握手协议、TLS记录协议 +HTTPS 经由 HTTP 进行通信,但利用 TLS 来保证安全,即 HTTPS = HTTP + TLS + +# 3. 从密码学的角度理解 HTTPS + +HTTPS 使用 TLS 保证安全,这里的“安全”分两部分,一是传输内容加密、二是服务端的身份认证 + +## 3.1. TLS 工作流程 + +![此图并不准确][2] +此为服务端单向认证,还有客户端/服务端双向认证,流程类似,只不过客户端也有自己的证书,并发送给服务器进行验证 + +## 3.2. 密码基础 + +### 3.2.1. 伪随机数生成器 + +为什么叫伪随机数,因为没有真正意义上的随机数,具体可以参考 Random/TheadLocalRandom +它的主要作用在于生成对称密码的秘钥、用于公钥密码生成秘钥对 + +### 3.2.2. 消息认证码 + +消息认证码主要用于验证消息的完整性与消息的认证,其中消息的认证指“消息来自正确的发送者” + +>消息认证码用于验证和认证,而不是加密 + +![消息认证码过程][3] + +1. 发送者与接收者事先共享秘钥 +2. 发送者根据发送消息计算 MAC 值 +3. 发送者发送消息和 MAC 值 +4. 接收者根据接收到的消息计算 MAC 值 +5. 接收者根据自己计算的 MAC 值与收到的 MAC 对比 +6. 如果对比成功,说明消息完整,并来自与正确的发送者 + +### 3.2.3. 数字签名 + +消息认证码的缺点在于**无法防止否认**,因为共享秘钥被 client、server 两端拥有,server 可以伪造 client 发送给自己的消息(自己给自己发送消息),为了解决这个问题,我们需要它们有各自的秘钥不被第二个知晓(这样也解决了共享秘钥的配送问题) + +![数字签名过程][4] + +>数字签名和消息认证码都**不是为了加密** +>可以将单向散列函数获取散列值的过程理解为使用 md5 摘要算法获取摘要的过程 + +使用自己的私钥对自己所认可的消息生成一个该消息专属的签名,这就是数字签名,表明我承认该消息来自自己 +注意:**私钥用于加签,公钥用于解签,每个人都可以解签,查看消息的归属人** + +### 3.2.4. 公钥密码 + +公钥密码也叫非对称密码,由公钥和私钥组成,它是最开始是为了解决秘钥的配送传输安全问题,即,我们不配送私钥,只配送公钥,私钥由本人保管 +它与数字签名相反,公钥密码的私钥用于解密、公钥用于加密,每个人都可以用别人的公钥加密,但只有对应的私钥才能解开密文 +client:明文 + 公钥 = 密文 +server:密文 + 私钥 = 明文 +注意:**公钥用于加密,私钥用于解密,只有私钥的归属者,才能查看消息的真正内容** + +### 3.2.5. 证书 + +证书:全称公钥证书(Public-Key Certificate, PKC),里面保存着归属者的基本信息,以及证书过期时间、归属者的公钥,并由认证机构(Certification Authority, **CA**)施加数字签名,表明,某个认证机构认定该公钥的确属于此人 + +>想象这个场景:你想在支付宝页面交易,你需要支付宝的公钥进行加密通信,于是你从百度上搜索关键字“支付宝公钥”,你获得了支什宝的公钥,这个时候,支什宝通过中间人攻击,让你访问到了他们支什宝的页面,最后你在这个支什宝页面完美的使用了支什宝的公钥完成了与支什宝的交易 +>![证书过程][5] + +在上面的场景中,你可以理解支付宝证书就是由支付宝的公钥、和给支付宝颁发证书的企业的数字签名组成 +任何人都可以给自己或别人的公钥添加自己的数字签名,表明:我拿我的尊严担保,我的公钥/别人的公钥是真的,至于信不信那是另一回事了 + +### 3.2.6. 密码小结 + +| 密码 | 作用 | 组成 | +| :-- | :-- | :-- | +| 消息认证码 | 确认消息的完整、并对消息的来源认证 | 共享秘钥+消息的散列值 | +| 数字签名 | 对消息的散列值签名 | 公钥+私钥+消息的散列值 | +| 公钥密码 | 解决秘钥的配送问题 | 公钥+私钥+消息 | +| 证书 | 解决公钥的归属问题 | 公钥密码中的公钥+数字签名 | + +## 3.3. TLS 使用的密码技术 + +1. 伪随机数生成器:秘钥生成随机性,更难被猜测 +2. 对称密码:对称密码使用的秘钥就是由伪随机数生成,相较于非对称密码,效率更高 +3. 消息认证码:保证消息信息的完整性、以及验证消息信息的来源 +4. 公钥密码:证书技术使用的就是公钥密码 +5. 数字签名:验证证书的签名,确定由真实的某个 CA 颁发 +6. 证书:解决公钥的真实归属问题,降低中间人攻击概率 + +## 3.4. TLS 总结 + +TLS 是一系列密码工具的框架,作为框架,它也是非常的灵活,体现在每个工具套件它都可以替换,即:客户端与服务端之间协商密码套件,从而更难的被攻破,例如使用不同方式的对称密码,或者公钥密码、数字签名生成方式、单向散列函数技术的替换等 + +# 4. RSA 简单示例 + +RSA 是一种公钥密码算法,我们简单的走一遍它的加密解密过程 +加密算法:密文 = (明文^E) mod N,其中公钥为{E,N},即”求明文的E次方的对 N 的余数“ +解密算法:明文 = (密文^D) mod N,其中秘钥为{D,N},即”求密文的D次方的对 N 的余数“ +例:我们已知公钥为{5,323},私钥为{29,323},明文为300,请写出加密和解密的过程: +>加密:密文 = 123 ^ 5 mod 323 = 225 +>解密:明文 = 225 ^ 29 mod 323 = [[(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 5) mod 323] * [(225 ^ 4) mod 323]] mod 323 = (4 * 4 * 4 * 4 * 4 * 290) mod 323 = 123 + +# 5. 参考 + +1. SSL加密发生在哪里: +2. TLS工作流程: +3. 《图解密码技术》: 豆瓣评分 9.5 + +[1]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E4%B8%83%E5%B1%82.png +[2]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/tls%E6%B5%81%E7%A8%8B.png +[3]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%B6%88%E6%81%AF%E8%AE%A4%E8%AF%81%E7%A0%81%E8%BF%87%E7%A8%8B.png +[4]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/%E6%95%B0%E5%AD%97%E7%AD%BE%E5%90%8D%E8%BF%87%E7%A8%8B.png +[5]: https://leran2deeplearnjavawebtech.oss-cn-beijing.aliyuncs.com/somephoto/dns%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB.png \ No newline at end of file diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" new file mode 100644 index 00000000000..5cc6dc1b14a --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/dubbo.md" @@ -0,0 +1,282 @@ +本文是作者根据官方文档以及自己平时的使用情况,对 Dubbo 所做的一个总结。如果不懂 Dubbo 的使用的话,可以参考我的这篇文章[《超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务》](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1902169190&lang=zh_CN#rd) + +Dubbo 官网:http://dubbo.apache.org/zh-cn/index.html + +Dubbo 中文文档: http://dubbo.apache.org/zh-cn/index.html + + + +- [一 重要的概念](#一-重要的概念) + - [1.1 什么是 Dubbo?](#11-什么是-dubbo) + - [1.2 什么是 RPC?RPC原理是什么?](#12-什么是-rpcrpc原理是什么) + - [1.3 为什么要用 Dubbo?](#13-为什么要用-dubbo) + - [1.4 什么是分布式?](#14-什么是分布式) + - [1.5 为什么要分布式?](#15-为什么要分布式) +- [二 Dubbo 的架构](#二-dubbo-的架构) + - [2.1 Dubbo 的架构图解](#21-dubbo-的架构图解) + - [2.2 Dubbo 工作原理](#22-dubbo-工作原理) +- [三 Dubbo 的负载均衡策略](#三-dubbo-的负载均衡策略) + - [3.1 先来解释一下什么是负载均衡](#31-先来解释一下什么是负载均衡) + - [3.2 再来看看 Dubbo 提供的负载均衡策略](#32-再来看看-dubbo-提供的负载均衡策略) + - [3.2.1 Random LoadBalance\(默认,基于权重的随机负载均衡机制\)](#321-random-loadbalance默认基于权重的随机负载均衡机制) + - [3.2.2 RoundRobin LoadBalance\(不推荐,基于权重的轮询负载均衡机制\)](#322-roundrobin-loadbalance不推荐基于权重的轮询负载均衡机制) + - [3.2.3 LeastActive LoadBalance](#323-leastactive-loadbalance) + - [3.2.4 ConsistentHash LoadBalance](#324-consistenthash-loadbalance) + - [3.3 配置方式](#33-配置方式) +- [四 zookeeper宕机与dubbo直连的情况](#四-zookeeper宕机与dubbo直连的情况) + + + + +## 一 重要的概念 + +### 1.1 什么是 Dubbo? + +Apache Dubbo (incubating) |ˈdʌbəʊ| 是一款高性能、轻量级的开源Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。简单来说 Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。 + +Dubbo 目前已经有接近 23k 的 Star ,Dubbo的Github 地址:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo) 。 另外,在开源中国举行的2018年度最受欢迎中国开源软件这个活动的评选中,Dubbo 更是凭借其超高人气仅次于 vue.js 和 ECharts 获得第三名的好成绩。 + +Dubbo 是由阿里开源,后来加入了 Apache 。正式由于 Dubbo 的出现,才使得越来越多的公司开始使用以及接受分布式架构。 + +**我们上面说了 Dubbo 实际上是 RPC 框架,那么什么是 RPC呢?** + +### 1.2 什么是 RPC?RPC原理是什么? + +**什么是 RPC?** + +RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。比如两个不同的服务A,B部署在两台不同的机器上,那么服务 A 如果想要调用服务 B 中的某个方法该怎么办呢?使用 HTTP请求 当然可以,但是可能会比较慢而且一些优化做的并不好。 RPC 的出现就是为了解决这个问题。 + +**RPC原理是什么?** + +我这里这是简单的提一下。详细内容可以查看下面这篇文章: + +[http://www.importnew.com/22003.html](http://www.importnew.com/22003.html) + +![RPC原理图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/37345851.jpg) + + +1. 服务消费方(client)调用以本地调用方式调用服务; +2. client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体; +3. client stub找到服务地址,并将消息发送到服务端; +4. server stub收到消息后进行解码; +5. server stub根据解码结果调用本地的服务; +6. 本地服务执行并将结果返回给server stub; +7. server stub将返回结果打包成消息并发送至消费方; +8. client stub接收到消息,并进行解码; +9. 服务消费方得到最终结果。 + +下面再贴一个网上的时序图: + +![RPC原理时序图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-6/32527396.jpg) + +**说了这么多,我们为什么要用 Dubbo 呢?** + +### 1.3 为什么要用 Dubbo? + +Dubbo 的诞生和 SOA 分布式架构的流行有着莫大的关系。SOA 面向服务的架构(Service Oriented Architecture),也就是把工程按照业务逻辑拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。SOA架构中有两个主要角色:服务提供者(Provider)和服务使用者(Consumer)。 + +![为什么要用 Dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/43050183.jpg) + +**如果你要开发分布式程序,你也可以直接基于 HTTP 接口进行通信,但是为什么要用 Dubbo呢?** + +我觉得主要可以从 Dubbo 提供的下面四点特性来说为什么要用 Dubbo: + +1. **负载均衡**——同一个服务部署在不同的机器时该调用那一台机器上的服务 +2. **服务调用链路生成**——随着系统的发展,服务越来越多,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。Dubbo 可以为我们解决服务之间互相是如何调用的。 +3. **服务访问压力以及时长统计、资源调度和治理**——基于访问压力实时管理集群容量,提高集群利用率。 +4. **服务降级**——某个服务挂掉之后调用备用服务 + +另外,Dubbo 除了能够应用在分布式系统中,也可以应用在现在比较火的微服务系统中。不过,由于 Spring Cloud 在微服务中应用更加广泛,所以,我觉得一般我们提 Dubbo 的话,大部分是分布式系统的情况。 + +**我们刚刚提到了分布式这个概念,下面再给大家介绍一下什么是分布式?为什么要分布式?** + +### 1.4 什么是分布式? + +分布式或者说 SOA 分布式重要的就是面向服务,说简单的分布式就是我们把整个系统拆分成不同的服务然后将这些服务放在不同的服务器上减轻单体服务的压力提高并发量和性能。比如电商系统可以简单地拆分成订单系统、商品系统、登录系统等等,拆分之后的每个服务可以部署在不同的机器上,如果某一个服务的访问量比较大的话也可以将这个服务同时部署在多台机器上。 + +### 1.5 为什么要分布式? + +从开发角度来讲单体应用的代码都集中在一起,而分布式系统的代码根据业务被拆分。所以,每个团队可以负责一个服务的开发,这样提升了开发效率。另外,代码根据业务拆分之后更加便于维护和扩展。 + +另外,我觉得将系统拆分成分布式之后不光便于系统扩展和维护,更能提高整个系统的性能。你想一想嘛?把整个系统拆分成不同的服务/系统,然后每个服务/系统 单独部署在一台服务器上,是不是很大程度上提高了系统性能呢? + +## 二 Dubbo 的架构 + +### 2.1 Dubbo 的架构图解 + +![Dubbo 架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/46816446.jpg) + +**上述节点简单说明:** + +- **Provider:** 暴露服务的服务提供方 +- **Consumer:** 调用远程服务的服务消费方 +- **Registry:** 服务注册与发现的注册中心 +- **Monitor:** 统计服务的调用次数和调用时间的监控中心 +- **Container:** 服务运行容器 + +**调用关系说明:** + +1. 服务容器负责启动,加载,运行服务提供者。 +2. 服务提供者在启动时,向注册中心注册自己提供的服务。 +3. 服务消费者在启动时,向注册中心订阅自己所需的服务。 +4. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 +5. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。 +6. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。 + +**重要知识点总结:** + +- **注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小** +- **监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示** +- **注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外** +- **注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者** +- **注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表** +- **注册中心和监控中心都是可选的,服务消费者可以直连服务提供者** +- **服务提供者无状态,任意一台宕掉后,不影响使用** +- **服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复** + + + + + +### 2.2 Dubbo 工作原理 + + +![Dubbo 工作原理](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-26/64702923.jpg) + +图中从下至上分为十层,各层均为单向依赖,右边的黑色箭头代表层之间的依赖关系,每一层都可以剥离上层被复用,其中,Service 和 Config 层为 API,其它各层均为 SPI。 + +**各层说明**: + +- 第一层:**service层**,接口层,给服务提供者和消费者来实现的 +- 第二层:**config层**,配置层,主要是对dubbo进行各种配置的 +- 第三层:**proxy层**,服务接口透明代理,生成服务的客户端 Stub 和服务器端 Skeleton +- 第四层:**registry层**,服务注册层,负责服务的注册与发现 +- 第五层:**cluster层**,集群层,封装多个服务提供者的路由以及负载均衡,将多个实例组合成一个服务 +- 第六层:**monitor层**,监控层,对rpc接口的调用次数和调用时间进行监控 +- 第七层:**protocol层**,远程调用层,封装rpc调用 +- 第八层:**exchange层**,信息交换层,封装请求响应模式,同步转异步 +- 第九层:**transport层**,网络传输层,抽象mina和netty为统一接口 +- 第十层:**serialize层**,数据序列化层。网络传输需要。 + + +## 三 Dubbo 的负载均衡策略 + +### 3.1 先来解释一下什么是负载均衡 + +**先来个官方的解释。** + +> 维基百科对负载均衡的定义:负载均衡改善了跨多个计算资源(例如计算机,计算机集群,网络链接,中央处理单元或磁盘驱动的的工作负载分布。负载平衡旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源的过载。使用具有负载平衡而不是单个组件的多个组件可以通过冗余提高可靠性和可用性。负载平衡通常涉及专用软件或硬件 + +**上面讲的大家可能不太好理解,再用通俗的话给大家说一下。** + +比如我们的系统中的某个服务的访问量特别大,我们将这个服务部署在了多台服务器上,当客户端发起请求的时候,多台服务器都可以处理这个请求。那么,如何正确选择处理该请求的服务器就很关键。假如,你就要一台服务器来处理该服务的请求,那该服务部署在多台服务器的意义就不复存在了。负载均衡就是为了避免单个服务器响应同一请求,容易造成服务器宕机、崩溃等问题,我们从负载均衡的这四个字就能明显感受到它的意义。 + +### 3.2 再来看看 Dubbo 提供的负载均衡策略 + +在集群负载均衡时,Dubbo 提供了多种均衡策略,默认为 `random` 随机调用。可以自行扩展负载均衡策略,参见:[负载均衡扩展](https://dubbo.gitbooks.io/dubbo-dev-book/content/impls/load-balance.html)。 + +备注:下面的图片来自于:尚硅谷2018Dubbo 视频。 + + +#### 3.2.1 Random LoadBalance(默认,基于权重的随机负载均衡机制) + +- **随机,按权重设置随机概率。** +- 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。 + +![基于权重的随机负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/77722327.jpg) + + + +#### 3.2.2 RoundRobin LoadBalance(不推荐,基于权重的轮询负载均衡机制) + +- 轮循,按公约后的权重设置轮循比率。 +- 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。 + +![基于权重的轮询负载均衡机制](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-7/97933247.jpg) + +#### 3.2.3 LeastActive LoadBalance + +- 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。 +- 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。 + +#### 3.2.4 ConsistentHash LoadBalance + +- **一致性 Hash,相同参数的请求总是发到同一提供者。(如果你需要的不是随机负载均衡,是要一类请求都到一个节点,那就走这个一致性hash策略。)** +- 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。 +- 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing +- 缺省只对第一个参数 Hash,如果要修改,请配置 `` +- 缺省用 160 份虚拟节点,如果要修改,请配置 `` + +### 3.3 配置方式 + +**xml 配置方式** + +服务端服务级别 + +```java + +``` +客户端服务级别 + +```java + +``` + +服务端方法级别 + +```java + + + +``` + +客户端方法级别 + +```java + + + +``` + +**注解配置方式:** + +消费方基于基于注解的服务级别配置方式: + +```java +@Reference(loadbalance = "roundrobin") +HelloService helloService; +``` + +## 四 zookeeper宕机与dubbo直连的情况 + +zookeeper宕机与dubbo直连的情况在面试中可能会被经常问到,所以要引起重视。 + +在实际生产中,假如zookeeper注册中心宕掉,一段时间内服务消费方还是能够调用提供方的服务的,实际上它使用的本地缓存进行通讯,这只是dubbo健壮性的一种提现。 + +**dubbo的健壮性表现:** + +1. 监控中心宕掉不影响使用,只是丢失部分采样数据 +2. 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务 +3. 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 +4. 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯 +5. 服务提供者无状态,任意一台宕掉后,不影响使用 +5. 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复 + +我们前面提到过:注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小。所以,我们可以完全可以绕过注册中心——采用 **dubbo 直连** ,即在服务消费方配置服务提供方的位置信息。 + + +**xml配置方式:** + +```xml + +``` + +**注解方式:** + +```java + @Reference(url = "127.0.0.1:20880") + HelloService helloService; +``` + + + diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" new file mode 100644 index 00000000000..e90a129a9a3 --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/message-queue.md" @@ -0,0 +1,152 @@ + + +- [消息队列其实很简单](#消息队列其实很简单) + - [一 什么是消息队列](#一-什么是消息队列) + - [二 为什么要用消息队列](#二-为什么要用消息队列) + - [\(1\) 通过异步处理提高系统性能(削峰、减少响应所需时间)](#1-通过异步处理提高系统性能削峰减少响应所需时间) + - [\(2\) 降低系统耦合性](#2-降低系统耦合性) + - [三 使用消息队列带来的一些问题](#三-使用消息队列带来的一些问题) + - [四 JMS VS AMQP](#四-jms-vs-amqp) + - [4.1 JMS](#41-jms) + - [4.1.1 JMS 简介](#411-jms-简介) + - [4.1.2 JMS两种消息模型](#412-jms两种消息模型) + - [4.1.3 JMS 五种不同的消息正文格式](#413-jms-五种不同的消息正文格式) + - [4.2 AMQP](#42-amqp) + - [4.3 JMS vs AMQP](#43-jms-vs-amqp) + - [五 常见的消息队列对比](#五-常见的消息队列对比) + + + + +# 消息队列其实很简单 + +  “RabbitMQ?”“Kafka?”“RocketMQ?”...在日常学习与开发过程中,我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手,又或者你是不懂消息队列的新手,不论你了不了解消息队列,本文都将带你搞懂消息队列的一些基本理论。如果你是老手,你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念,如果你是新手,相信本文将是你打开消息队列大门的一板砖。 + +## 一 什么是消息队列 + +  我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用。消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰、降低系统耦合性。目前使用较多的消息队列有ActiveMQ,RabbitMQ,Kafka,RocketMQ,我们后面会一一对比这些消息队列。 + +  另外,我们知道队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。比如生产者发送消息1,2,3...对于消费者就会按照1,2,3...的顺序来消费。但是偶尔也会出现消息被消费的顺序不对的情况,比如某个消息消费失败又或者一个 queue 多个consumer 也会导致消息被消费的顺序不对,我们一定要保证消息被消费的顺序正确。 + +  除了上面说的消息消费顺序的问题,使用消息队列,我们还要考虑如何保证消息不被重复消费?如何保证消息的可靠性传输(如何处理消息丢失的问题)?......等等问题。所以说使用消息队列也不是十全十美的,使用它也会让系统可用性降低、复杂度提高,另外需要我们保障一致性等问题。 + +## 二 为什么要用消息队列 + +  我觉得使用消息队列主要有两点好处:1.通过异步处理提高系统性能(削峰、减少响应所需时间);2.降低系统耦合性。如果在面试的时候你被面试官问到这个问题的话,一般情况是你在你的简历上涉及到消息队列这方面的内容,这个时候推荐你结合你自己的项目来回答。 + + +  《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。 + +### (1) 通过异步处理提高系统性能(削峰、减少响应所需时间) + +![通过异步处理提高系统性能](https://user-gold-cdn.xitu.io/2018/4/21/162e63a8e34ba534?w=910&h=350&f=jpeg&s=29123) +  如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。** + +  通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示: +![合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击](https://user-gold-cdn.xitu.io/2018/4/21/162e64583dd3ed01?w=780&h=384&f=jpeg&s=13550) + +  因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。 + +### (2) 降低系统耦合性 + +  我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。 + +  我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示: + +![利用消息队列实现事件驱动结构](https://user-gold-cdn.xitu.io/2018/4/21/162e6665fa394b3b?w=790&h=290&f=jpeg&s=14946) + +  **消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。 + +  消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。 + +  **另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。** + +**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。**除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。** 另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了 5 种消息模型。 + +## 三 使用消息队列带来的一些问题 + +- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了! +- **系统复杂性提高:** 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题! +- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了! + +## 四 JMS VS AMQP + +### 4.1 JMS + +#### 4.1.1 JMS 简介 + +  JMS(JAVA Message Service,java消息服务)是java的消息服务,JMS的客户端之间可以通过JMS服务进行异步的消息传输。**JMS(JAVA Message Service,Java消息服务)API是一个消息服务的标准或者说是规范**,允许应用程序组件基于JavaEE平台创建、发送、接收和读取消息。它使分布式通信耦合度更低,消息服务更加可靠以及异步性。 + +**ActiveMQ 就是基于 JMS 规范实现的。** + +#### 4.1.2 JMS两种消息模型 + +①点到点(P2P)模型 + +![点到点(P2P)模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7185572ca37d?w=575&h=135&f=gif&s=8530) +  使用**队列(Queue)**作为消息通信载体;满足**生产者与消费者模式**,一条消息只能被一个消费者使用,未被消费的消息在队列中保留直到被消费或超时。比如:我们生产者发送100条消息的话,两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半(也就是你一个我一个的消费。) + +② 发布/订阅(Pub/Sub)模型 + + ![发布/订阅(Pub/Sub)模型](https://user-gold-cdn.xitu.io/2018/4/21/162e7187c268eaa5?w=402&h=164&f=gif&s=15492) +  发布订阅模型(Pub/Sub) 使用**主题(Topic)**作为消息通信载体,类似于**广播模式**;发布者发布一条消息,该消息通过主题传递给所有的订阅者,**在一条消息广播之后才订阅的用户则是收不到该条消息的**。 + +#### 4.1.3 JMS 五种不同的消息正文格式 + +  JMS定义了五种不同的消息正文格式,以及调用的消息类型,允许你发送并接收以一些不同形式的数据,提供现有消息格式的一些级别的兼容性。 + +- StreamMessage -- Java原始值的数据流 +- MapMessage--一套名称-值对 +- TextMessage--一个字符串对象 +- ObjectMessage--一个序列化的 Java对象 +- BytesMessage--一个字节的数据流 + + +### 4.2 AMQP + +  ​ AMQP,即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准 **高级消息队列协议**(二进制应用层协议),是应用层协议的一个开放标准,为面向消息的中间件设计,兼容 JMS。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件同产品,不同的开发语言等条件的限制。 + +**RabbitMQ 就是基于 AMQP 协议实现的。** + + + +### 4.3 JMS vs AMQP + + +|对比方向| JMS | AMQP | +| :-------- | --------:| :--: | +| 定义| Java API | 协议 | +| 跨语言 | 否 | 是 | +| 跨平台 | 否 | 是 | +| 支持消息类型 | 提供两种消息模型:①Peer-2-Peer;②Pub/sub| 提供了五种消息模型:①direct exchange;②fanout exchange;③topic change;④headers exchange;⑤system exchange。本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分;| +|支持消息类型| 支持多种消息类型 ,我们在上面提到过| byte[](二进制)| + +**总结:** + +- AMQP 为消息定义了线路层(wire-level protocol)的协议,而JMS所定义的是API规范。在 Java 体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差。而AMQP天然具有跨平台、跨语言特性。 +- JMS 支持TextMessage、MapMessage 等复杂的消息类型;而 AMQP 仅支持 byte[] 消息类型(复杂的类型可序列化后发送)。 +- 由于Exchange 提供的路由算法,AMQP可以提供多样化的路由方式来传递消息到消息队列,而 JMS 仅支持 队列 和 主题/订阅 方式两种。 + + +## 五 常见的消息队列对比 + + + +对比方向 |概要 +-------- | --- + 吞吐量| 万级的 ActiveMQ 和 RabbitMQ 的吞吐量(ActiveMQ 的性能最差)要比 十万级甚至是百万级的 RocketMQ 和 Kafka 低一个数量级。 +可用性| 都可以实现高可用。ActiveMQ 和 RabbitMQ 都是基于主从架构实现高可用性。RocketMQ 基于分布式架构。 kafka 也是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 +时效性| RabbitMQ 基于erlang开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。其他三个都是 ms 级。 +功能支持| 除了 Kafka,其他三个功能都较为完备。 Kafka 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 +消息丢失| ActiveMQ 和 RabbitMQ 丢失的可能性非常低, RocketMQ 和 Kafka 理论上不会丢失。 + + +**总结:** + +- ActiveMQ 的社区算是比较成熟,但是较目前来说,ActiveMQ 的性能比较差,而且版本迭代很慢,不推荐使用。 +- RabbitMQ 在吞吐量方面虽然稍逊于 Kafka 和 RocketMQ ,但是由于它基于 erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 erlang 开发,所以国内很少有公司有实力做erlang源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这四种消息队列中,RabbitMQ 一定是你的首选。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。 +- RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的MQ,并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。RocketMQ 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ 挺好的 +- kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 + + +参考:《Java工程师面试突击第1季-中华石杉老师》 diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" new file mode 100644 index 00000000000..825f71239c6 --- /dev/null +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/rabbitmq.md" @@ -0,0 +1,324 @@ + + +- [一文搞懂 RabbitMQ 的重要概念以及安装](#一文搞懂-rabbitmq-的重要概念以及安装) + - [一 RabbitMQ 介绍](#一-rabbitmq-介绍) + - [1.1 RabbitMQ 简介](#11-rabbitmq-简介) + - [1.2 RabbitMQ 核心概念](#12-rabbitmq-核心概念) + - [1.2.1 Producer(生产者) 和 Consumer(消费者)](#121-producer生产者-和-consumer消费者) + - [1.2.2 Exchange(交换器)](#122-exchange交换器) + - [1.2.3 Queue(消息队列)](#123-queue消息队列) + - [1.2.4 Broker(消息中间件的服务节点)](#124-broker消息中间件的服务节点) + - [1.2.5 Exchange Types(交换器类型)](#125-exchange-types交换器类型) + - [① fanout](#①-fanout) + - [② direct](#②-direct) + - [③ topic](#③-topic) + - [④ headers(不推荐)](#④-headers不推荐) + - [二 安装 RabbitMq](#二-安装-rabbitmq) + - [2.1 安装 erlang](#21-安装-erlang) + - [2.2 安装 RabbitMQ](#22-安装-rabbitmq) + + + +# 一文搞懂 RabbitMQ 的重要概念以及安装 + +## 一 RabbitMQ 介绍 + +这部分参考了 《RabbitMQ实战指南》这本书的第 1 章和第 2 章。 + +### 1.1 RabbitMQ 简介 + +RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。 + +RabbitMQ 发展到今天,被越来越多的人认可,这和它在易用性、扩展性、可靠性和高可用性等方面的卓著表现是分不开的。RabbitMQ 的具体特点可以概括为以下几点: + +- **可靠性:** RabbitMQ使用一些机制来保证消息的可靠性,如持久化、传输确认及发布确认等。 +- **灵活的路由:** 在消息进入队列之前,通过交换器来路由消息。对于典型的路由功能,RabbitMQ 己经提供了一些内置的交换器来实现。针对更复杂的路由功能,可以将多个交换器绑定在一起,也可以通过插件机制来实现自己的交换器。这个后面会在我们将 RabbitMQ 核心概念的时候详细介绍到。 +- **扩展性:** 多个RabbitMQ节点可以组成一个集群,也可以根据实际业务情况动态地扩展集群中节点。 +- **高可用性:** 队列可以在集群中的机器上设置镜像,使得在部分节点出现问题的情况下队列仍然可用。 +- **支持多种协议:** RabbitMQ 除了原生支持 AMQP 协议,还支持 STOMP、MQTT 等多种消息中间件协议。 +- **多语言客户端:** RabbitMQ几乎支持所有常用语言,比如 Java、Python、Ruby、PHP、C#、JavaScript等。 +- **易用的管理界面:** RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息、集群中的节点等。在安装 RabbitMQ 的时候会介绍到,安装好 RabbitMQ 就自带管理界面。 +- **插件机制:** RabbitMQ 提供了许多插件,以实现从多方面进行扩展,当然也可以编写自己的插件。感觉这个有点类似 Dubbo 的 SPI机制。 + +### 1.2 RabbitMQ 核心概念 + +RabbitMQ 整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。可以把消息传递的过程想象成:当你将一个包裹送到邮局,邮局会暂存并最终将邮件通过邮递员送到收件人的手上,RabbitMQ就好比由邮局、邮箱和邮递员组成的一个系统。从计算机术语层面来说,RabbitMQ 模型更像是一种交换机模型。 + +下面再来看看图1—— RabbitMQ 的整体模型架构。 + +![图1-RabbitMQ 的整体模型架构](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/96388546.jpg) + +下面我会一一介绍上图中的一些概念。 + +#### 1.2.1 Producer(生产者) 和 Consumer(消费者) + +- **Producer(生产者)** :生产消息的一方(邮件投递者) +- **Consumer(消费者)** :消费消息的一方(邮件收件人) + +消息一般由 2 部分组成:**消息头**(或者说是标签 Label)和 **消息体**。消息体也可以称为 payLoad ,消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括 routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。生产者把消息交由 RabbitMQ 后,RabbitMQ 会根据消息头把消息发送给感兴趣的 Consumer(消费者)。 + +#### 1.2.2 Exchange(交换器) + +在 RabbitMQ 中,消息并不是直接被投递到 **Queue(消息队列)** 中的,中间还必须经过 **Exchange(交换器)** 这一层,**Exchange(交换器)** 会把我们的消息分配到对应的 **Queue(消息队列)** 中。 + +**Exchange(交换器)** 用来接收生产者发送的消息并将这些消息路由给服务器中的队列中,如果路由不到,或许会返回给 **Producer(生产者)** ,或许会被直接丢弃掉 。这里可以将RabbitMQ中的交换器看作一个简单的实体。 + +**RabbitMQ 的 Exchange(交换器) 有4种类型,不同的类型对应着不同的路由策略**:**direct(默认)**,**fanout**, **topic**, 和 **headers**,不同类型的Exchange转发消息的策略有所区别。这个会在介绍 **Exchange Types(交换器类型)** 的时候介绍到。 + +Exchange(交换器) 示意图如下: + +![Exchange(交换器) 示意图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/24007899.jpg) + +生产者将消息发给交换器的时候,一般会指定一个 **RoutingKey(路由键)**,用来指定这个消息的路由规则,而这个 **RoutingKey 需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效**。 + +RabbitMQ 中通过 **Binding(绑定)** 将 **Exchange(交换器)** 与 **Queue(消息队列)** 关联起来,在绑定的时候一般会指定一个 **BindingKey(绑定建)** ,这样 RabbitMQ 就知道如何正确将消息路由到队列了,如下图所示。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和 Queue 的绑定可以是多对多的关系。 + +Binding(绑定) 示意图: + +![Binding(绑定) 示意图](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/70553134.jpg) + +生产者将消息发送给交换器时,需要一个RoutingKey,当 BindingKey 和 RoutingKey 相匹配时,消息会被路由到对应的队列中。在绑定多个队列到同一个交换器的时候,这些绑定允许使用相同的 BindingKey。BindingKey 并不是在所有的情况下都生效,它依赖于交换器类型,比如fanout类型的交换器就会无视,而是将消息路由到所有绑定到该交换器的队列中。 + +#### 1.2.3 Queue(消息队列) + +**Queue(消息队列)** 用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 + +**RabbitMQ** 中消息只能存储在 **队列** 中,这一点和 **Kafka** 这种消息中间件相反。Kafka 将消息存储在 **topic(主题)** 这个逻辑层面,而相对应的队列逻辑只是topic实际存储文件中的位移标识。 RabbitMQ 的生产者生产消息并最终投递到队列中,消费者可以从队列中获取消息并消费。 + +**多个消费者可以订阅同一个队列**,这时队列中的消息会被平均分摊(Round-Robin,即轮询)给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,这样避免的消息被重复消费。 + +**RabbitMQ** 不支持队列层面的广播消费,如果有广播消费的需求,需要在其上进行二次开发,这样会很麻烦,不建议这样做。 + +#### 1.2.4 Broker(消息中间件的服务节点) + +对于 RabbitMQ 来说,一个 RabbitMQ Broker 可以简单地看作一个 RabbitMQ 服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个 RabbitMQ Broker 看作一台 RabbitMQ 服务器。 + +下图展示了生产者将消息存入 RabbitMQ Broker,以及消费者从Broker中消费数据的整个流程。 + +![消息队列的运转过程](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/67952922.jpg) + +这样图1中的一些关于 RabbitMQ 的基本概念我们就介绍完毕了,下面再来介绍一下 **Exchange Types(交换器类型)** 。 + +#### 1.2.5 Exchange Types(交换器类型) + +RabbitMQ 常用的 Exchange Type 有 **fanout**、**direct**、**topic**、**headers** 这四种(AMQP规范里还提到两种 Exchange Type,分别为 system 与 自定义,这里不予以描述)。 + +##### ① fanout + +fanout 类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中,不需要做任何判断操作,所以 fanout 类型是所有的交换机类型里面速度最快的。fanout 类型常用来广播消息。 + +##### ② direct + +direct 类型的Exchange路由规则也很简单,它会把消息路由到那些 Bindingkey 与 RoutingKey 完全匹配的 Queue 中。 + +![direct 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/37008021.jpg) + +以上图为例,如果发送消息的时候设置路由键为“warning”,那么消息会路由到 Queue1 和 Queue2。如果在发送消息的时候设置路由键为"Info”或者"debug”,消息只会路由到Queue2。如果以其他的路由键发送消息,则消息不会路由到这两个队列中。 + +direct 类型常用在处理有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。 + +##### ③ topic + +前面讲到direct类型的交换器路由规则是完全匹配 BindingKey 和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队列中,但这里的匹配规则有些不同,它约定: + +- RoutingKey 为一个点号“.”分隔的字符串(被点号“.”分隔开的每一段独立的字符串称为一个单词),如 “com.rabbitmq.client”、“java.util.concurrent”、“com.hidden.client”; +- BindingKey 和 RoutingKey 一样也是点号“.”分隔的字符串; +- BindingKey 中可以存在两种特殊字符串“*”和“#”,用于做模糊匹配,其中“.”用于匹配一个单词,“#”用于匹配多个单词(可以是零个)。 + +![topic 类型交换器](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-16/73843.jpg) + +以上图为例: + +- 路由键为 “com.rabbitmq.client” 的消息会同时路由到 Queuel 和 Queue2; +- 路由键为 “com.hidden.client” 的消息只会路由到 Queue2 中; +- 路由键为 “com.hidden.demo” 的消息只会路由到 Queue2 中; +- 路由键为 “java.rabbitmq.demo” 的消息只会路由到Queuel中; +- 路由键为 “java.util.concurrent” 的消息将会被丢弃或者返回给生产者(需要设置 mandatory 参数),因为它没有匹配任何路由键。 + +##### ④ headers(不推荐) + +headers 类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中的 headers 属性进行匹配。在绑定队列和交换器时制定一组键值对,当发送消息到交换器时,RabbitMQ会获取到该消息的 headers(也是一个键值对的形式)'对比其中的键值对是否完全匹配队列和交换器绑定时指定的键值对,如果完全匹配则消息会路由到该队列,否则不会路由到该队列。headers 类型的交换器性能会很差,而且也不实用,基本上不会看到它的存在。 + +## 二 安装 RabbitMq + +通过 Docker 安装非常方便,只需要几条命令就好了,我这里是只说一下常规安装方法。 + +前面提到了 RabbitMQ 是由 Erlang语言编写的,也正因如此,在安装RabbitMQ 之前需要安装 Erlang。 + +注意:在安装 RabbitMQ 的时候需要注意 RabbitMQ 和 Erlang 的版本关系,如果不注意的话会导致出错,两者对应关系如下: + +![RabbitMQ 和 Erlang 的版本关系](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RabbitMQ-Erlang.png) + +### 2.1 安装 erlang + +**1 下载 erlang 安装包** + +在官网下载然后上传到 Linux 上或者直接使用下面的命令下载对应的版本。 + +```shell +[root@SnailClimb local]#wget http://erlang.org/download/otp_src_19.3.tar.gz +``` + +erlang 官网下载:[http://www.erlang.org/downloads](http://www.erlang.org/downloads) + + **2 解压 erlang 安装包** + +```shell +[root@SnailClimb local]#tar -xvzf otp_src_19.3.tar.gz +``` + +**3 删除 erlang 安装包** + +```shell +[root@SnailClimb local]#rm -rf otp_src_19.3.tar.gz +``` + +**4 安装 erlang 的依赖工具** + +```shell +[root@SnailClimb local]#yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel unixODBC-devel +``` + +**5 进入erlang 安装包解压文件对 erlang 进行安装环境的配置** + +新建一个文件夹 + +```shell +[root@SnailClimb local]# mkdir erlang +``` + +对 erlang 进行安装环境的配置 + +```shell +[root@SnailClimb otp_src_19.3]# +./configure --prefix=/usr/local/erlang --without-javac +``` + +**6 编译安装** + +```shell +[root@SnailClimb otp_src_19.3]# +make && make install +``` + +**7 验证一下 erlang 是否安装成功了** + +```shell +[root@SnailClimb otp_src_19.3]# ./bin/erl +``` +运行下面的语句输出“hello world” + +```erlang + io:format("hello world~n", []). +``` +![输出“hello world”](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/49570541.jpg) + +大功告成,我们的 erlang 已经安装完成。 + +**8 配置 erlang 环境变量** + +```shell +[root@SnailClimb etc]# vim profile +``` + +追加下列环境变量到文件末尾 + +```shell +#erlang +ERL_HOME=/usr/local/erlang +PATH=$ERL_HOME/bin:$PATH +export ERL_HOME PATH +``` + +运行下列命令使配置文件`profile`生效 + +```shell +[root@SnailClimb etc]# source /etc/profile +``` + +输入 erl 查看 erlang 环境变量是否配置正确 + +```shell +[root@SnailClimb etc]# erl +``` + +![输入 erl 查看 erlang 环境变量是否配置正确](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/62504246.jpg) + +### 2.2 安装 RabbitMQ + +**1. 下载rpm** + +```shell +wget https://www.rabbitmq.com/releases/rabbitmq-server/v3.6.8/rabbitmq-server-3.6.8-1.el7.noarch.rpm +``` +或者直接在官网下载 + +https://www.rabbitmq.com/install-rpm.html[enter link description here](https://www.rabbitmq.com/install-rpm.html) + +**2. 安装rpm** + +```shell +rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc +``` +紧接着执行: + +```shell +yum install rabbitmq-server-3.6.8-1.el7.noarch.rpm +``` +中途需要你输入"y"才能继续安装。 + +**3 开启 web 管理插件** + +```shell +rabbitmq-plugins enable rabbitmq_management +``` + +**4 设置开机启动** + +```shell +chkconfig rabbitmq-server on +``` + +**4. 启动服务** + +```shell +service rabbitmq-server start +``` + +**5. 查看服务状态** + +```shell +service rabbitmq-server status +``` + +**6. 访问 RabbitMQ 控制台** + +浏览器访问:http://你的ip地址:15672/ + +默认用户名和密码: guest/guest;但是需要注意的是:guestuest用户只是被容许从localhost访问。官网文档描述如下: + +```shell +“guest” user can only connect via localhost +``` + +**解决远程访问 RabbitMQ 远程访问密码错误** + +新建用户并授权 + +```shell +[root@SnailClimb rabbitmq]# rabbitmqctl add_user root root +Creating user "root" ... +[root@SnailClimb rabbitmq]# rabbitmqctl set_user_tags root administrator + +Setting tags for user "root" to [administrator] ... +[root@SnailClimb rabbitmq]# +[root@SnailClimb rabbitmq]# rabbitmqctl set_permissions -p / root ".*" ".*" ".*" +Setting permissions for user "root" in vhost "/" ... + +``` + +再次访问:http://你的ip地址:15672/ ,输入用户名和密码:root root + +![RabbitMQ控制台](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-12/45835332.jpg) + + diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" index 2df02d0312c..7840d844d73 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\346\225\260\346\215\256\351\200\232\344\277\241(RESTful\343\200\201RPC\343\200\201\346\266\210\346\201\257\351\230\237\345\210\227).md" @@ -55,7 +55,7 @@ [《消息队列深入解析》](https://blog.csdn.net/qq_34337272/article/details/80029918) -当前使用较多的消息队列有ActiveMQ(性能差,不推荐使用)、RabbitMQ、RocketMQ、Kafka等等,我们之前提高的redis数据库也可以实现消息队列,不过不推荐,redis本身设计就不是用来做消息队列的。 +当前使用较多的消息队列有ActiveMQ(性能差,不推荐使用)、RabbitMQ、RocketMQ、Kafka等等,我们之前提到的redis数据库也可以实现消息队列,不过不推荐,redis本身设计就不是用来做消息队列的。 - **ActiveMQ:** ActiveMQ是Apache出品,最流行的,能力强劲的开源消息总线。ActiveMQ是一个完全支持JMS1.1和J2EE 1.4规范的JMSProvider实现,尽管JMS规范出台已经是很久的事情了,但是JMS在当今的J2EE应用中间仍然扮演着特殊的地位。 @@ -80,7 +80,7 @@ [《十分钟入门RocketMQ》](http://jm.taobao.org/2017/01/12/rocketmq-quick-start-in-10-minutes/) (阿里中间件团队博客) -- **Kafka**:Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统,Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。 +- **Kafka**:Kafka是一个分布式的、可分区的、可复制的、基于发布/订阅的消息系统(现在官方的描述是“一个分布式流平台”),Kafka主要用于大数据领域,当然在分布式系统中也有应用。目前市面上流行的消息队列RocketMQ就是阿里借鉴Kafka的原理、用Java开发而得。 具体可以参考: diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" index 63f9962a784..813ba89c983 100644 --- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" +++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\270\216\346\225\260\346\215\256\351\200\232\344\277\241/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.md" @@ -2,41 +2,42 @@ - [一 OSI与TCP/IP各层的结构与功能,都有哪些协议](#一-osi与tcpip各层的结构与功能都有哪些协议) - - [五层协议的体系结构](#五层协议的体系结构) - - [1 应用层](#1-应用层) - - [域名系统](#域名系统) - - [HTTP协议](#http协议) - - [2 运输层](#2-运输层) - - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议) - - [UDP 的主要特点](#udp-的主要特点) - - [TCP 的主要特点](#tcp-的主要特点) - - [3 网络层](#3-网络层) - - [4 数据链路层](#4-数据链路层) - - [5 物理层](#5-物理层) - - [总结一下](#总结一下) + - [五层协议的体系结构](#五层协议的体系结构) + - [1 应用层](#1-应用层) + - [域名系统](#域名系统) + - [HTTP协议](#http协议) + - [2 运输层](#2-运输层) + - [运输层主要使用以下两种协议](#运输层主要使用以下两种协议) + - [UDP 的主要特点](#udp-的主要特点) + - [TCP 的主要特点](#tcp-的主要特点) + - [3 网络层](#3-网络层) + - [4 数据链路层](#4-数据链路层) + - [5 物理层](#5-物理层) + - [总结一下](#总结一下) - [二 TCP 三次握手和四次挥手\(面试常客\)](#二-tcp-三次握手和四次挥手面试常客) - - [为什么要三次握手](#为什么要三次握手) - - [为什么要传回 SYN](#为什么要传回-syn) - - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) - - [为什么要四次挥手](#为什么要四次挥手) + - [为什么要三次握手](#为什么要三次握手) + - [为什么要传回 SYN](#为什么要传回-syn) + - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack) + - [为什么要四次挥手](#为什么要四次挥手) - [三 TCP、UDP 协议的区别](#三-tcp、udp-协议的区别) - [四 TCP 协议如何保证可靠传输](#四-tcp-协议如何保证可靠传输) - - [停止等待协议](#停止等待协议) - - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议) - - [连续ARQ协议](#连续arq协议) - - [滑动窗口](#滑动窗口) - - [流量控制](#流量控制) - - [拥塞控制](#拥塞控制) + - [停止等待协议](#停止等待协议) + - [自动重传请求 ARQ 协议](#自动重传请求-arq-协议) + - [连续ARQ协议](#连续arq协议) + - [滑动窗口](#滑动窗口) + - [流量控制](#流量控制) + - [拥塞控制](#拥塞控制) - [五 在浏览器中输入url地址 ->> 显示主页的过程(面试常客)](#五-在浏览器中输入url地址---显示主页的过程(面试常客)) - [六 状态码](#六-状态码) - [七 各种协议与HTTP协议之间的关系](#七-各种协议与http协议之间的关系) - [八 HTTP长连接、短连接](#八-http长连接、短连接) - [写在最后](#写在最后) - - [计算机网络常见问题回顾](#计算机网络常见问题回顾) - - [建议](#建议) + - [计算机网络常见问题回顾](#计算机网络常见问题回顾) + - [建议](#建议) + 相对与上一个版本的计算机网路面试知识总结,这个版本增加了 “TCP协议如何保证可靠传输”包括超时重传、停止等待协议、滑动窗口、流量控制、拥塞控制等内容并且对一些已有内容做了补充。 @@ -68,7 +69,7 @@ #### 运输层主要使用以下两种协议 -1. **传输控制协议 TCP**(Transmisson Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。 +1. **传输控制协议 TCP**(Transmission Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。 2. **用户数据协议 UDP**(User Datagram Protocol)--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。 #### UDP 的主要特点 @@ -96,7 +97,7 @@ 这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称. -互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Prococol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP层**。 +互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Intert Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP层**。 ### 4 数据链路层 **数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装程帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。 @@ -156,6 +157,7 @@ ![TCP四次挥手](https://user-gold-cdn.xitu.io/2018/5/8/1633e1676e2ac0a3?w=500&h=340&f=jpeg&s=13406) 断开一个 TCP 连接则需要“四次挥手”: + - 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送 - 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加1 。和 SYN 一样,一个 FIN 将占用一个序号 - 服务器-关闭与客户端的连接,发送一个FIN给客户端 @@ -185,7 +187,8 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接, 4. TCP 的接收端会丢弃重复的数据。 5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制) 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。 -7. **停止等待协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 +7. **停止等待协议** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。 +8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。 @@ -219,7 +222,7 @@ TCP 提供面向连接的服务。在传送数据之前必须先建立连接, 2. B收到重复的M1后,也直接丢弃重复的M1。 ### 自动重传请求 ARQ 协议 -停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重转时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。 +停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为自动重传请求ARQ。 **优点:** 简单 @@ -266,10 +269,24 @@ TCP的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 百度好像最喜欢问这个问题。 > 打开一个网页,整个过程会使用哪些协议 -图片来源:《图解HTTP》 +图解(图片来源:《图解HTTP》): ![状态码](https://user-gold-cdn.xitu.io/2018/4/19/162db5e985aabdbe?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) +总体来说分为以下几个过程: + +1. DNS解析 +2. TCP连接 +3. 发送HTTP请求 +4. 服务器处理请求并返回HTTP报文 +5. 浏览器解析渲染页面 +6. 连接结束 + +具体可以参考下面这篇文章: + +- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700) + + ## 六 状态码 @@ -314,13 +331,12 @@ Connection:keep-alive 非常推荐大家看一下 《图解HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。 -参考: - -[https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250) -[https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466) +### 参考 -[https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) +- [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250) +- [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466) +- [https://blog.csdn.net/turn__back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641) diff --git "a/\351\227\262\350\260\210/2018 summary.md" "b/\351\227\262\350\260\210/2018 summary.md" new file mode 100644 index 00000000000..8d82e40b876 --- /dev/null +++ "b/\351\227\262\350\260\210/2018 summary.md" @@ -0,0 +1,174 @@ +# 【2018总结】即使平凡,也要热爱自己的生活 + +2018 年于我而讲,虽然平凡,但是自己就是在这平凡的一年也收货了很多东西。不光是自己学到的知识,我觉得 2018 年最大的幸运有三:其一是自己拥有了一份爱情,一份甜蜜的初恋,我真的很幸运遇到我现在的女朋友,愿以后的日子都能有她;其一是在 2018 年,我拥有了一份自己还算满意的 offer,马上就要毕业了,自己也要正式进去社会了;其一是自己在 2018 年的实现了自己的经济独立,这是一件让我很高兴的事情,我觉得大在学生时代实现经济独立还算是一件很不错的事情,花了这么多年父母的辛苦钱,自己也终于能替他们分担一点了。2018 年,感恩父母,感恩老师,感恩朋友,感恩遇到的每个善良的人,同时感恩2018年那个还算努力的自己。2019 继续加油! + +## 一份甜蜜的初恋(分手) + +先说说爱情。我和我的女朋友在一起已经半年多了,准确的来说截止到今天也就是 2018-12-30 号已经 190 天了。 + +![我和傻欢](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/54482393.jpg) + +我俩是异地,我在荆州,她在三亚。相见一面不管是时间上还是经济上对于还是学生的我们来说都甚是不易。见过很多人议论异地恋的种种不好,但是,于我而言,一份好的感情是值得被等待的。“待每一天如初恋,互相尊重彼此生活,共同努力,等老了就退隐山林养老......”,这应该是我和她之间最好的承诺了。 + +## 还算不错的学习收获 + +再来说说学习。这一年还算是让人满意,虽然我也不知道这一年自己到底学到了什么。如果你要问我这一年在学习上做的最满意的事情是什么,我还真不好回答,下面就从下面几个维度来简单谈一谈。 + +### 开源 + +这一年自己在Github上还是挺活跃的,提交了很多的代码和文档,同时也收获了很多的star、follower、pr、issue以及fork。 + +![我的Github概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/41250517.jpg) + +![我的Github贡献概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/33580466.jpg) + +开源的Java学习/面试指南—JavaGuide 某种程度上让我挺满意的,3月份开源 ,到现在的18k+ star 也算是可以用厚积薄发来形容了。但是,JavaGuide 也有很多让我不满意的,在2019年以及以后我也会继续完善。JavaGuide 地址:[https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) + +![JavaGuide 概览](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) + +### 技术博客 + +我更新的博客主要都是关于Java方面的,也更新了几篇Python的,有一篇Python的文章竟然在我的CSDN上面阅读霸榜。 +![霸榜的 Python 文章](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-30/19527688.jpg) + +在这一年,我更新了挺多技术文章,这里就不一一列举了,我贴一下自己觉得不错的文章吧! + +#### 最常见面试题系列 + +- [最最最常见的Java面试题总结——第一周](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484252&idx=1&sn=cb160d67fc1c0a95babc464b703df5e7&chksm=fd98553dcaefdc2b18f934957dd950aeaf04e90136099fa2817fffbd1e1df452b581e1caee17&token=1398134989&lang=zh_CN#rd) +- [最最最常见的Java面试题总结——第二周](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484282&idx=1&sn=7f986dc3263b6ca0f9e182145fdd40a1&chksm=fd98551bcaefdc0d5aff9577692881dc79765a339ce97e55958e23e1956aa7092dfac44b68f1&token=1398134989&lang=zh_CN#rd) +- [这几道Java集合框架面试题在面试中几乎必问](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484308&idx=1&sn=e3607919aed604be629617f867f46844&chksm=fd9855f5caefdce3f1ee72cb33b9b3bf9899fa2b64bbb92f1e820c0ef3985245b1f7dfc05358&token=1398134989&lang=zh_CN#rd) +- [如果不会这几道多线程基础题,请自觉面壁!](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484337&idx=1&sn=d5e953d4b2da7ed37a7f843bfb437ed8&chksm=fd9855d0caefdcc65cb2e5cc0c69d27f785fc41477bcf55fff2cdff3268b0b078eb1a5107726&token=1398134989&lang=zh_CN#rd) +- [值得立马保存的 synchronized 关键字总结](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484355&idx=1&sn=6da29974b6dd1a4aa0d032f44d5fa8de&chksm=fd9855a2caefdcb4c370814baafd4baca27dfccaf609c9edf82370637ba4856176ab143a375e&token=1398134989&lang=zh_CN#rd) +- [【面试必备】源码角度一步一步分析 ArrayList 扩容机制](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=1&sn=1b6155015fedfc9f78fabecc18da7b18&chksm=fd985591caefdc870cb018d27f92e1908b6c6e22816a77ead03c4e44b2f53caec00871172b1f&token=1398134989&lang=zh_CN#rd) +- [Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&token=1398134989&lang=zh_CN#rd) + +#### Github + +- [近几个月Github上最热门的Java项目一览](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484188&idx=1&sn=40037de4844f62316465bbe4e910c69c&chksm=fd98557dcaefdc6bedcaeb275aae7c340d46cf6ab0dc96e49c51982f9c53d6a44de283efc9a8&token=1398134989&lang=zh_CN#rd) +- [推荐10个Java方向最热门的开源项目(8月)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484333&idx=1&sn=8c97b029692877a537d55175a8c82977&chksm=fd9855cccaefdcdaffe0558ba5e8dca415495935b0ad1181e6b148b08e1c86ce5d841e9df901&token=1398134989&lang=zh_CN#rd) +- [Github上 Star 数相加超过 7w+ 的三个面试相关的仓库推荐](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484644&idx=1&sn=5016caaf97e498b76de2189e3f55e9dc&chksm=fd985285caefdb93f4e3c7545d30edac6ad31b99f1fcc4503350101f0b20bba9a9705ed7d124&token=1398134989&lang=zh_CN#rd) +- [11月 Github Trending 榜最热门的 10 个 Java 项目](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484730&idx=1&sn=86e35dfea1478221b6d14a263e88ac89&chksm=fd98535bcaefda4d4f03bf0cd2e0a8fd9f44b1a2b118457a0c8b3de2ff8a1f4c4b7cd083f40e&token=1398134989&lang=zh_CN#rd) +- [盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=1398134989&lang=zh_CN#rd) + + +#### 备战面试系列 + +- [可能是一份最适合你的后端面试指南(部分内容前端同样适用)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484529&idx=1&sn=9c7a3d6ad124affcadc19b0ff49bf68a&chksm=fd985210caefdb0615a9643fa698cb6267e89562730423841d942cde17ec9c1280dfc3a2b933&token=1398134989&lang=zh_CN#rd) +- [【备战春招/秋招系列1】程序员的简历就该这样写](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484573&idx=1&sn=8c5965d4a3710d405d8e8cc10c7b0ce5&chksm=fd9852fccaefdbea8dfe0bc40188b7579f1cddb1e8905dc981669a3f21d2a04cadceafa9023f&token=1990180468&lang=zh_CN#rd) +- [【备战春招/秋招系列2】初出茅庐的程序员该如何准备面试?](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484578&idx=1&sn=eea72d80a2325257f00aaed21d5b226f&chksm=fd9852c3caefdbd52dd8a537cc723ed1509314401b3a669a253ef5bc0360b6fddef48b9c2e94&token=1990180468&lang=zh_CN#rd) +- [【备战春招/秋招系列3】Java程序员必备书单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484592&idx=1&sn=6d9731ce7401be49e97c1af6ed384ecc&chksm=fd9852d1caefdbc720a361ae65a8ad9d53cfb4800b15a7c68cbdc630b313215c6c52e0934ec2&token=1990180468&lang=zh_CN#rd) +- [【备战春招/秋招系列】美团面经总结基础篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484601&idx=1&sn=4907b7fef0856791c565d49d788ba8cc&chksm=fd9852d8caefdbce88e51c0a10a4ec77c97f382fd2af4a840ea47cffc828bfd0f993f50d5f0d&token=1895808268&lang=zh_CN#rd) +- [【备战春招/秋招系列】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=1895808268&lang=zh_CN#rd) +- [【备战春招/秋招系列】美团Java面经总结终结篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484668&idx=1&sn=9d4631588393075d9c453f307410f0cd&chksm=fd98529dcaefdb8b5497d1f161834af6917c33ea3d305eb41872e522707fa94218769ca60101&token=1398134989&lang=zh_CN#rd) +- [GitHub 上四万 Star 大佬的求职回忆](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484739&idx=1&sn=25cf5b36090f69299150663bdccfeec2&chksm=fd985322caefda34df0734efa607114704d1937f083aee2230b797d1f5aa04f7d13bf2f81dc5&token=1398134989&lang=zh_CN#rd)(非原创) + + +#### 并发编程面试必备 + +- [并发编程面试必备:synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 和ReenTrantLock 的对比](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484539&idx=1&sn=3500cdcd5188bdc253fb19a1bfa805e6&chksm=fd98521acaefdb0c5167247a1fa903a1a53bb4e050b558da574f894f9feda5378ec9d0fa1ac7&scene=21#wechat_redirect) +- [并发编程面试必备:JUC 中的 Atomic 原子类总结](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484553&idx=1&sn=aca9fa19f723206eff7e33a10973a887&chksm=fd9852e8caefdbfe7180c34f83bbb422a1a0bef1ed44b1e84f56924244ea3fd2da720f25c6dd#rd) +- [并发编程面试必备:AQS 原理以及 AQS 同步组件总结](http://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484559&idx=1&sn=28dae85c38c4c500201c39234d25d731&chksm=fd9852eecaefdbf80cc54a25204e7c7d81170ce659acf92b7fa4151799ca3d0d7df2225d4ff1#rd) +- [并发编程面试必备:BATJ都爱问的多线程面试题](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484564&idx=1&sn=d8467fdc5c1b3883e9b99485f7b0fb9a&chksm=fd9852f5caefdbe364d1c438865cff84acd8f40c1c9e2f9f5c8fef673b30f905b4c5f5255368&token=1398134989&lang=zh_CN#rd) + +#### 虚拟机 + +- [可能是把Java内存区域讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484303&idx=1&sn=af0fd436cef755463f59ee4dd0720cbd&chksm=fd9855eecaefdcf8d94ac581cfda4e16c8a730bda60c3b50bc55c124b92f23b6217f7f8e58d5&token=1398134989&lang=zh_CN#rd) +- [搞定 JVM 垃圾回收就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484328&idx=1&sn=214f5e18a6afa096eb552fd8627e0cea&chksm=fd9855c9caefdcdf70c746c74d31f65bbb109eedaea0cfe311a1e10af666047df59ff04c873b&token=1398134989&lang=zh_CN#rd) + +#### Spring Boot + +- [超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484706&idx=1&sn=d413fc17023482f67ca17cb6756b9ff8&chksm=fd985343caefda555969568fdf4734536e0a1745f9de337d434a7dbd04e893bd2d75f3641aab&token=1398134989&lang=zh_CN#rd) +- [基于 SpringBoot2.0+优雅整合 SpringBoot+Mybatis](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484730&idx=2&sn=9be4636dd9a416b46f9029df68fad232&chksm=fd98535bcaefda4dccf14a286a24fcd2b3d4ab0d0e4d89dfbc955df99d2b06a1e17392b3c10b&token=1398134989&lang=zh_CN#rd) +- [新手也能实现,基于SpirngBoot2.0+ 的 SpringBoot+Mybatis 多数据源配置](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484737&idx=1&sn=e39693d845f022d689437ee58948ef6a&chksm=fd985320caefda36d5ab8abd52f5516c11cc5d1104608695bcea5909602b28dc40c132d6d46c&token=1398134989&lang=zh_CN#rd) +- [SpringBoot 整合 阿里云OSS 存储服务,快来免费搭建一个自己的图床](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484745&idx=1&sn=dbeec694916d204605929244d48a6b1c&chksm=fd985328caefda3e793170d81433c7c0b7dc1c4a4ae99395cce23b1d1a239482fd5bf1d89bc6&token=1398134989&lang=zh_CN#rd) + +#### 成长 + +- [结束了我短暂的秋招,说点自己的感受](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484516&idx=1&sn=4e2320613e76dd73a130c63beebbc3ca&chksm=fd985205caefdb13b4b611ed3c604d95314d28d567ec0c3b44585b89a7dc3142bcd52bc2d4cb&token=1398134989&lang=zh_CN#rd) +- [保研之路:从双非到南大](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484477&idx=1&sn=3b597e2431611aacca2b5d671a309d85&chksm=fd98525ccaefdb4a7e3742b5958244d453efe26f61f42f9f108190a0c18313f083189f10944e&token=1398134989&lang=zh_CN#rd)(非原创) +- [【周日闲谈】最近想说的几件小事](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484650&idx=1&sn=e97ea1eeebdb5def58bae1949bec9448&chksm=fd98528bcaefdb9d76ac62fd10544f058b1fee4a40ffd06ab9312b7eb3a62f86d67ea653b88a&token=1398134989&lang=zh_CN#rd) +- [这7个问题,可能大部分Java程序员都比较关心吧!](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484836&idx=1&sn=a6ada99c9506af01dc3bb472f66c57be&chksm=fd9853c5caefdad3034dbed00cf04412ea990fc05b6168720e6828ae6c90c9a885793acd7a14&token=1398134989&lang=zh_CN#rd) + +#### Docker + +- [可能是把Docker的概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484127&idx=1&sn=70ee95619ec761da884c4f9af3e83194&chksm=fd9854becaefdda81a02bf6cf9bd07a2fc879efa7cefc79691a0d319b501d8572e8bad981d87&token=1398134989&lang=zh_CN#rd) + +#### Linux + +- [后端程序员必备的Linux基础知识](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484157&idx=1&sn=8b47e623e83fb3666bce7c680e4649b8&chksm=fd98549ccaefdd8ad815f3d8eaca86cc7e7245b4f8de1d23897af3017f5fdb3f152734c40f5e&token=1398134989&lang=zh_CN#rd) +- [快速入门大厂后端面试必备的 Shell 编程](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484696&idx=1&sn=d3c1ba5abc10c10ff844cae2109a2628&chksm=fd985379caefda6faff8e050b7dfa1e92fbfe2912e44150cb4ae349aea807836166355062970&token=1398134989&lang=zh_CN#rd) + +#### ZooKeeper + +- [可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484379&idx=1&sn=036f5d3defa8a6979afb77acc82a9517&chksm=fd9855bacaefdcacc1462f781b634e5599f2ee9e806bd24297dae4af0e4196a70ca6bbd8c354&token=1398134989&lang=zh_CN#rd) + +#### Redis + +- [redis 总结——重构版](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484450&idx=1&sn=7ee03fa67aecd05c5becd2a8259d3631&chksm=fd985243caefdb554ebab9149e750ac0c819074c57bd208f2d7f097fbc461ed58223e71c05f1&token=1398134989&lang=zh_CN#rd) +- [史上最全Redis高可用技术解决方案大全](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484478&idx=1&sn=a1250d9b8025cd7cb6fc6a58238ab51e&chksm=fd98525fcaefdb499a027df0138c98d4b02d828f27bd6144a4d40a1c088d340c29dd53d4a026&token=1398134989&lang=zh_CN#rd)(非原创) + +#### 计算机网络 + +- [搞定计算机网络面试,看这篇就够了(补充版)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484289&idx=1&sn=6b556843c60aac9a17b0e7c2e3cd6bca&chksm=fd9855e0caefdcf6af4123b719448c81d90c5442d4052ae01a4698047e226c0c18c14b2cc54a&token=1398134989&lang=zh_CN#rd) + +#### 数据库 + +- [【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1398134989&lang=zh_CN#rd) + +#### 消息队列 + +- [新手也能看懂,消息队列其实很简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484789&idx=1&sn=ba972f0aac39e9a28b29ddf92fc15c18&chksm=fd985314caefda0278235427d43846b6374ff32f4149352dec063287cbf9733b888acbb79923&token=1398134989&lang=zh_CN#rd) +- [一文搞懂 RabbitMQ 的重要概念以及安装](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484796&idx=1&sn=bc56fecb634732669cfe7db8d1820ded&chksm=fd98531dcaefda0b07b8a9c13429ef225d36a6e287c96c53d7aa3dfd65c62ccd60d13b22ebbf&token=1398134989&lang=zh_CN#rd) + +### 读书 + +推荐一下 2018 年看过的书籍,小部分可能2017年也看过一些。 + +#### 已看完 + +- [《图解HTTP》](https://book.douban.com/subject/25863515/)(推荐,豆瓣评分 8.1 , 1.6K+人评价): 讲漫画一样的讲HTTP,很有意思,不会觉得枯燥,大概也涵盖也HTTP常见的知识点。因为篇幅问题,内容可能不太全面。不过,如果不是专门做网络方向研究的小伙伴想研究HTTP相关知识的话,读这本书的话应该来说就差不多了。 +- [《大话数据结构》](https://book.douban.com/subject/6424904/)(推荐,豆瓣评分 7.9 , 1K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有数据结构基础或者说数据结构没学好的小伙伴用来入门数据结构。 +- [《算法图解》](https://book.douban.com/subject/26979890/)(推荐,豆瓣评分 8.4,0.6K+人评价):入门类型的书籍,读起来比较浅显易懂,适合没有算法基础或者说算法没学好的小伙伴用来入门。示例丰富,图文并茂,以让人容易理解的方式阐释了算法.读起来比较快,内容不枯燥! +- [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/)(推荐,豆瓣评分 7.2,0.2K+人评价): 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 +- [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/)(推荐):豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 +- [《深入理解Java虚拟机(第2版)周志明》](https://book.douban.com/subject/24722612/)(推荐,豆瓣评分 8.9,1.0K+人评价):建议多刷几遍,书中的所有知识点可以通过JAVA运行时区域和JAVA的内存模型与线程两个大模块罗列完全。 +- [《Netty实战》](https://book.douban.com/subject/27038538/)(推荐,豆瓣评分 7.8,92人评价):内容很细,如果想学Netty的话,推荐阅读这本书! +- [《从Paxos到Zookeeper》](https://book.douban.com/subject/26292004/)(推荐,豆瓣评分 7.8,0.3K人评价):简要介绍几种典型的分布式一致性协议,以及解决分布式一致性问题的思路,其中重点讲解了Paxos和ZAB协议。同时,本书深入介绍了分布式一致性问题的工业解决方案——ZooKeeper,并着重向读者展示这一分布式协调框架的使用方法、内部实现及运维技巧,旨在帮助读者全面了解ZooKeeper,并更好地使用和运维ZooKeeper。 +- [《Redis实战》](https://book.douban.com/subject/26612779/):如果你想了解Redis的一些概念性知识的话,这本书真的非常不错。 +- [《Redis设计与实现》](https://book.douban.com/subject/25900156/)(推荐,豆瓣评分 8.5,0.5K+人评价) +- [《大型网站技术架构:核心原理与案例分析+李智慧》](https://book.douban.com/subject/25723064/)(推荐):这本书我读过,基本不需要你有什么基础啊~读起来特别轻松,但是却可以学到很多东西,非常推荐了。另外我写过这本书的思维导图,关注我的微信公众号:“Java面试通关手册”回复“大型网站技术架构”即可领取思维导图。 + + +#### 未看完 + + +- [《鸟哥的Linux私房菜》](https://book.douban.com/subject/4889838/)(推荐,,豆瓣评分 9.1,0.3K+人评价):本书是最具知名度的Linux入门书《鸟哥的Linux私房菜基础学习篇》的最新版,全面而详细地介绍了Linux操作系统。全书分为5个部分:第一部分着重说明Linux的起源及功能,如何规划和安装Linux主机;第二部分介绍Linux的文件系统、文件、目录与磁盘的管理;第三部分介绍文字模式接口 shell和管理系统的好帮手shell脚本,另外还介绍了文字编辑器vi和vim的使用方法;第四部分介绍了对于系统安全非常重要的Linux账号的管理,以及主机系统与程序的管理,如查看进程、任务分配和作业管理;第五部分介绍了系统管理员(root)的管理事项,如了解系统运行状况、系统服务,针对登录文件进行解析,对系统进行备份以及核心的管理等。 +- [《亿级流量网站架构核心技术》](https://book.douban.com/subject/26999243/)(推荐):一书总结并梳理了亿级流量网站高可用和高并发原则,通过实例详细介绍了如何落地这些原则。本书分为四部分:概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术,让读者看后能快速运用到实践项目中。 +- [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/)(推荐):豆瓣 9.1 分,重构书籍的开山鼻祖。 +- [《深入剖析Tomcat》](https://book.douban.com/subject/10426640/)(推荐,豆瓣评分 8.4,0.2K+人评价):本书深入剖析Tomcat 4和Tomcat 5中的每个组件,并揭示其内部工作原理。通过学习本书,你将可以自行开发Tomcat组件,或者扩展已有的组件。 读完这本书,基本可以摆脱背诵面试题的尴尬。 +- [《高性能MySQL》](https://book.douban.com/subject/23008813/)(推荐,豆瓣评分 9.3,0.4K+人评价):mysql 领域的经典之作,拥有广泛的影响力。不但适合数据库管理员(dba)阅读,也适合开发人员参考学习。不管是数据库新手还是专家,相信都能从本书有所收获。 +- [深入理解Nginx(第2版)](https://book.douban.com/subject/26745255/):作者讲的非常细致,注释都写的都很工整,对于 Nginx 的开发人员非常有帮助。优点是细致,缺点是过于细致,到处都是代码片段,缺少一些抽象。 +- [《RabbitMQ实战指南》](https://book.douban.com/subject/27591386/):《RabbitMQ实战指南》从消息中间件的概念和RabbitMQ的历史切入,主要阐述RabbitMQ的安装、使用、配置、管理、运维、原理、扩展等方面的细节。如果你想浅尝RabbitMQ的使用,这本书是你最好的选择;如果你想深入RabbitMQ的原理,这本书也是你最好的选择;总之,如果你想玩转RabbitMQ,这本书一定是最值得看的书之一 +- [《Spring Cloud微服务实战》](https://book.douban.com/subject/27025912/):从时下流行的微服务架构概念出发,详细介绍了Spring Cloud针对微服务架构中几大核心要素的解决方案和基础组件。对于各个组件的介绍,《Spring Cloud微服务实战》主要以示例与源码结合的方式来帮助读者更好地理解这些组件的使用方法以及运行原理。同时,在介绍的过程中,还包含了作者在实践中所遇到的一些问题和解决思路,可供读者在实践中作为参考。 +- [《第一本Docker书》](https://book.douban.com/subject/26780404/):Docker入门书籍! +- [《Effective java 》](https://book.douban.com/subject/3360807/)(推荐,豆瓣评分 9.0,1.4K+人评价):本书介绍了在Java编程中78条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。通过对Java平台设计专家所使用的技术的全面描述,揭示了应该做什么,不应该做什么才能产生清晰、健壮和高效的代码。本书中的每条规则都以简短、独立的小文章形式出现,并通过例子代码加以进一步说明。本书内容全面,结构清晰,讲解详细。可作为技术人员的参考用书。 +- [《算法 第四版》](https://book.douban.com/subject/10432347/)(推荐,豆瓣评分 9.3,0.4K+人评价):Java语言描述,算法领域经典的参考书,全面介绍了关于算法和数据结构的必备知识,并特别针对排序、搜索、图处理和字符串处理进行了论述。书的内容非常多,可以说是Java程序员的必备书籍之一了。 + +## 一些个人愚见 + +### 关于读书 + +不知道大家收藏栏是不是和我一样收藏了很多文章,但是有多少篇是你真真认真看的呢?或者那几篇你认真看之后,经过一个月之后还记得这篇文章的大概内容。现在这个社会真是一个信息爆炸的社会,我个人真的深有感触,就在刚刚我还取消关注了好多微信公众号,很多公众号给我推的文章都有好几十篇了,但是我一篇都没有看,所以索性取消关注,省个位置。我个人觉得遇到好的文章,我们不光要读,而且要记录下来。就拿我本人来说,我平时喜欢用 OneNote 来记录学习笔记以及其他我感觉重要的事情比如重要人的生日啦这些。每当遇到自己喜欢的文章的时候,我都先会把文章的地址保存到我分好类的笔记本上,我会先进行第一遍阅读,第一遍我会读的很仔细,如果晦涩难懂的话我会先快速把总体看一遍,然后在细细品读。一般第二遍的时候我就会在笔记本上记录这篇文章的一些要点,以便我日后看到这些要点可以快速回忆起整篇文章的内容。如果某篇文章的知识点太过庞大的话,我会去选择采用思维导图的方式展示要点。看视频一样,看教学视频的话,如果觉得老师讲的不错,我们不妨记录下来,Onenote 或者有道云笔记都行,记录大概,够我们日后回忆就好。 + +### 关于学习 + +做事不要有功利性,我最早在掘金写文章,其实也只是为了记录自己的学习,没想到会有人喜欢自己的文章,另外我课外学的很多东西,我自己也不清楚以后工作会不会用到,反正我自己感觉即然自己感兴趣,那么就去学吧。我相信,很多东西可能暂时带给你不了带多实质性的帮助,但是总有一天它会对你有帮助。如果感到迷茫的话,就做好眼前的事(拿我们班主任的话说,如果你感到迷茫,你就学好现在的专业知识就好了),我觉得没毛病。 + +### 关于个人 + +在生活中一定要保持谦虚,保持谦虚,保持谦虚,时刻都要有反省的准备,你要记住学无止境,永远不要满足现在的现状。另外,就是一定要掌控好自己的时间,多留点时间给父母亲人,以及那些自己在乎的人。如果对别人很在乎的话,不要去装作不在乎,因为这样真的不是太好,虽然我之前也会这样,很多时候撰写的消息,最后没发出去。 + +## 最后分享一句话 + +分享给大家,我笔记本里一直保存的杨绛老先生的一句话:“我们曾如此渴望命运的波澜,到最后才发现:人生最曼妙的风景,竟是内心的淡定与从容……我们曾如此期盼外界的认可,到最后才知道:世界是自己的,与他人毫无关系!”。 diff --git "a/\351\227\262\350\260\210/2018 \347\247\213\346\213\233.md" "b/\351\227\262\350\260\210/2018 \347\247\213\346\213\233.md" new file mode 100644 index 00000000000..a2b47de8eee --- /dev/null +++ "b/\351\227\262\350\260\210/2018 \347\247\213\346\213\233.md" @@ -0,0 +1,93 @@ + + +# 秋招历程流水账总结 + +笔主大四准毕业生,在秋招末流比较幸运地进入了一家自己非常喜欢一家公司——ThoughtWorks. + +![今天去签约在门外拍的照片](https://images.gitbook.cn/1433af10-d5f7-11e8-841a-4f0b0cc7be7b) + +从9-6号投递出去第一份简历,到10-18号左右拿到第一份 offer ,中间差不多有 1 个半月的时间了。可能自己比较随缘,而且自己所在的大学所处的位置并不是互联网比较发达的城市的原因。所以,很少会有公司愿意跑到我们学校那边来宣讲,来的公司也大多是一些自己没听过或者不太喜欢的公司。所以,在前期,我仅仅能够通过网上投递简历的方式来找工作。 + +零零总总算了一下,自己在网上投了大概有 10 份左右的简历,都是些自己还算喜欢的公司。简单说一下自己投递的一些公司:网上投递的公司有:ThoughtWorks、网易、小米、携程、爱奇艺、知乎、小红书、搜狐、欢聚时代、京东;直接邮箱投递的有:烽火、中电数据、蚂蚁金服花呗部门、今日头条;线下宣讲会投递的有:玄武科技。 + +网上投递的大部分简历都是在做完笔试之后就没有了下文了,即使有几场笔试自我感觉做的很不错的情况下,还是没有收到后续的面试邀请。还有些邮箱投递的简历,后面也都没了回应。所以,我总共也只参加了3个公司的面试,ThoughtWorks、玄武科技和中电数据,都算是拿到了 offer。拿到 ThoughtWorks 的 offer之后,后面的一些笔试和少部分面试都拒了。决定去 ThoughtWorks 了,春招的大部队会没有我的存在。 + + +我个人对 ThoughtWorks 最有好感,ThoughtWorks 也是我自己之前很想去的一家公司。不光是因为我投递简历的时候可以不用重新填一遍表格可以直接发送我已经编辑好的PDF格式简历的友好,这个公司的文化也让我很喜欢。每次投递一家公司几乎都要重新填写一遍简历真的很让人头疼,即使是用牛客网的简历助手也还是有很多东西需要自己重新填写。 + +说句实话,自己在拿到第一份 offer 之前心里还是比较空的,虽然说对自己还是比较自信。包括自己当时来到武汉的原因,也是因为自己没有 offer ,就感觉心里空空的,我相信很多人在这个时候与我也有一样的感觉。然后,我就想到武汉参加一下别的学校宣讲会。现在看来,这个决定也是不必要的,因为我最后去的公司 ThoughtWorks,虽然就在我租的房子的附近,但之前投递的时候,选择的还是远程面试。来到武汉,简单的修整了一下之后,我就去参加了玄武科技在武理工的宣讲会,顺便做了笔试,然后接着就是技术面、HR面、高管面。总体来说,玄武科技的 HR 真的很热情,为他们点个赞,虽然自己最后没能去玄武科技,然后就是技术面非常简单,HR面和高管面也都还好,不会有压抑的感觉,总体聊得很愉快。需要注意的是 玄武科技和很多公司一样都有笔试中有逻辑题,我之前没有做过类似的题,所以当时第一次做有点懵逼。高管面的时候,高管还专门在我做的逻辑题上聊了一会,让我重新做了一些做错的题,并且给他讲一些题的思路,可以看出高层对于应聘者的这项能力还是比较看重的。 + + + +中电数据的技术面试是电话进行的,花了1个多小时一点,个人感觉问的还是比较深的,感觉自己总体回答的还是比较不错的。 + +这里我着重说一下 ThoughtWorks,也算是给想去 ThoughtWorks 的同学一点小小的提示。我是 9.11 号在官网:https://join.thoughtworks.cn/ 投递的简历,9.20 日邮件通知官网下载作业,作业总体来说不难,9.21 号花了半天多的时间做完,然后就直接在9.21 号下午提交了。然后等了挺长时间的,可能是因为 ThoughtWorks 在管理方面比较扁平化的原因,所以总体来说效率可能不算高。因为我选的是远程面试,所以直接下载好 zoom 之后,等HR打电话过来告诉你一个房间号,你就可以直接进去面试就好,一般技术面试有几个人看着你。技术面试的内容,首先就是在面试官让你在你之前做的作业的基础上新增加一个或者两个功能(20分钟)。所以,你在技术面试之前一定要保证你的程序的扩展性是不错的,另外就是你在技术面试之前最好能重构一下自己写的程序。重构本身就是你自己对你写的程序的理解加强很好的一种方式,另外重构也能让你发现你的程序的一些小问题。然后,这一步完成之后,面试官可能会问你一些基础问题,比较简单,所以我觉得 ThoughtWorks 可能更看重你的代码质量。ThoughtWorks 的 HR 面和其他公司的唯一不同可能在于,他会让你用英语介绍一下自己或者说自己的技术栈啊这些。 + +![思特沃克可爱的招聘官网](https://images.gitbook.cn/83f765e0-d5f6-11e8-9c1a-919e09988420) + + +# 关于面试一些重要的问题总结 +另外,再给大家总结一些我个人想到一些关于面试非常重要的一些问题。 + +### 面试前 + +**如何准备** + + +运筹帷幄之后,决胜千里之外!不打毫无准备的仗,我觉得大家可以先从下面几个方面来准备面试: + +1. 自我介绍。(你可千万这样介绍:“我叫某某,性别,来自哪里,学校是那个,自己爱干什么”,记住:多说点简历上没有的,多说点自己哪里比别人强!) +2. 自己面试中可能涉及哪些知识点、那些知识点是重点。 +3. 面试中哪些问题会被经常问到、面试中自己改如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!) +4. 自己的简历该如何写。 + + + +另外,如果你想去类似阿里巴巴、腾讯这种比较大的互联网公司的话,一定要尽早做打算。像阿里巴巴在7月份左右就开始了提前批招聘,到了9月份差不多就已经招聘完毕了。所以,秋招没有参加到阿里的面试还是很遗憾的,毕竟面试即使失败了,也能从阿里难度Max的面试中学到很多东西。 + +**关于着装** + +穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。 + +**关于自我介绍** + +如果你简历上写的基本信息就不要说了,比如性别、年龄、学校。另外,你也不要一上来就说自己爱好什么这方面内容。因为,面试官根本不关心这些东西。你直接挑和你岗位相关的重要经历和自己最突出的特点讲就好了。 + + + +**提前准备** + +面试之前可以在网上找找有没有你要面试的公司的面经。在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。 + + +### 面试中 + +面试的时候一定要自信,千万不要怕自己哪里会答不出来,或者说某个问题自己忘记怎么回答了。面试过程中,很多问题可能是你之前没有碰到过的,这个时候你就要通过自己构建的知识体系来思考这些问题。如果某些问题你回答不上来,你也可以让面试官给你简单的提示一下。总之,你要自信,你自信的前提是自己要做好充分的准备。下面给大家总结一些面试非常常见的问题: + +- SpringMVC 工作原理 +- 说一下自己对 IOC 、AOP 的理解 +- Spring 中用到了那些设计模式,讲一下自己对于这些设计模式的理解 +- Spring Bean 的作用域和生命周期了解吗 +- Spring 事务中的隔离级别 +- Spring 事务中的事务传播行为 +- 手写一个 LRU 算法 +- 知道那些排序算法,简单介绍一下快排的原理,能不能手写一下快排 +- String 为什么是不可变的?String为啥要设计为不可变的? +- Arraylist 与 LinkedList 异同 +- HashMap的底层实现 +- HashMap 的长度为什么是2的幂次方 +- ConcurrentHashMap 和 Hashtable 的区别 +- ConcurrentHashMap线程安全的具体实现方式/底层具体实现 +- 如果你的简历写了redis 、dubbo、zookeeper、docker的话,面试官还会问一下这些东西。比如redis可能会问你:为什么要用 redis、为什么要用 redis 而不用 map/guava 做缓存、redis 常见数据结构以及使用场景分析、 redis 设置过期时间、redis 内存淘汰机制、 redis 持久化机制、 缓存雪崩和缓存穿透问题、如何解决 Redis 的并发竞争 Key 问题、如何保证缓存与数据库双写时的数据一致性。 +- 一些简单的 Linux 命令。 +- 为什么要用 消息队列 +- 关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁②synchronized 和 ReenTrantLock 区别以及 volatile 和 synchronized 的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题,为什么要用线程池 ⑥Synchronized 关键字使用、底层原理、JDK1.6 之后的底层优化以及 ReenTrantLock 对比;⑦线程池使用时的注意事项、⑧AQS 原理以及 AQS 同步组件:Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock、⑨ReentranLock源码,设计原理,整体过程 等等问题。 +- 关于 Java 虚拟机问的比较多的是:①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题。 + + +### 面试后 + +如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油! + + + diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/2018-12.md" "b/\351\227\262\350\260\210/JavaGithubTrending/2018-12.md" new file mode 100644 index 00000000000..fc433c64f8e --- /dev/null +++ "b/\351\227\262\350\260\210/JavaGithubTrending/2018-12.md" @@ -0,0 +1,113 @@ +本文数据统计于 1.1 号凌晨,由 SnailClimb 整理。 + +### 1. JavaGuide + +- **Github地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) +- **star**: 18.2k +- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 + +![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/96151465.jpg) + +概览: + + ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) + +### 2. mall + +- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall) +- **star**: 3.3k +- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。 + +![mall](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/11382760.jpg) + +概览: + +![mall](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/99819963.jpg) + +### 3. advanced-java + +- **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) +- **star**: 3.3k +- **介绍**: 互联网 Java 工程师进阶知识完全扫盲 + +![advanced-java](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/95209476.jpg) + +概览: + +![advanced-java](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/18005792.jpg) + +### 4. matrix + +- **Github地址**:[https://github.com/Tencent/matrix](https://github.com/Tencent/matrix) +- **star**: 2.5k +- **介绍**: Matrix 是一款微信研发并日常使用的 APM(Application Performance Manage),当前主要运行在 Android 平台上。 Matrix 的目标是建立统一的应用性能接入框架,通过各种性能监控方案,对性能监控项的异常数据进行采集和分析,输出相应的问题分析、定位与优化建议,从而帮助开发者开发出更高质量的应用。 + +![matrix](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/8991895.jpg) + +### 5. miaosha + +- **Github地址**:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha) +- **star**: 2.4k +- **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。 + +![miaosha](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/79382302.jpg) + +### 6. arthas + +- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas) +- **star**: 8.2k +- **介绍**: Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱。 + +![arthas](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/73394671.jpg) + +### 7 spring-boot + +- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot) +- **star:** 32.6k +- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。 + + **关于Spring Boot官方的介绍:** + + > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可) + +![spring-boot](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/29649807.jpg) + +### 8. tutorials + +- **Github地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials) +- **star**: 10k +- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然,它们的重点是Spring Framework - Spring,Spring Boot和Spring Securiyt。 除了Spring之外,还有以下技术:核心Java,Jackson,HttpClient,Guava。 + +![tutorials](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/94425339.jpg) + +### 9. qmq + +- **Github地址**:[https://github.com/qunarcorp/qmq](https://github.com/qunarcorp/qmq) +- **star**: 1.1k +- **介绍**: QMQ是去哪儿网内部广泛使用的消息中间件,自2012年诞生以来在去哪儿网所有业务场景中广泛的应用,包括跟交易息息相关的订单场景; 也包括报价搜索等高吞吐量场景。 + +![arthas](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/73394671.jpg) + +### 10. symphony + +- **Github地址**:[https://github.com/b3log/symphony](https://github.com/b3log/symphony) +- **star**: 9k +- **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。 + +![symphony](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/11577890.jpg) + +### 11. incubator-dubbo + +- **Github地址**:[https://github.com/apache/incubator-dubbo](https://github.com/apache/incubator-dubbo) +- **star**: 23.6k +- **介绍**: 阿里开源的一个基于Java的高性能开源RPC框架。 + +![incubator-dubbo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/53068060.jpg) + +### 12. apollo + +- **Github地址**:[https://github.com/ctripcorp/apollo](https://github.com/ctripcorp/apollo) +- **star**: 10k +- **介绍**: Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 + +![apollo](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/89386993.jpg) \ No newline at end of file diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/2019-1.md" "b/\351\227\262\350\260\210/JavaGithubTrending/2019-1.md" new file mode 100644 index 00000000000..eecc4e2860e --- /dev/null +++ "b/\351\227\262\350\260\210/JavaGithubTrending/2019-1.md" @@ -0,0 +1,114 @@ +### 1. JavaGuide + +- **Github地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) +- **star**: 22.8k +- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 + +![JavaGuide](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/JavaGuide.png) + +**概览:** + + ![JavaGuide](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-24/1352784.jpg) + +### 2. advanced-java + +- **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) +- **star**: 7.9k +- **介绍**: 互联网 Java 工程师进阶知识完全扫盲 + +![advanced-java](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/advanced-java.png) + +**概览:** + +![advanced-java](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/18005792.jpg) + +### 3. fescar + +- **Github地址**:[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar) +- **star**: 4.6k +- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。)关于 fescar 的更详细介绍可以查看:[Github 上日获 800多 star 的阿里微服务架构分布式事务解决方案 FESCAR开源啦](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484888&idx=2&sn=ff9fe077c95959ec777c866a425bddbe&chksm=fd9853b9caefdaaf52a1b7caecc697938c2c1c5f6916527d1309ef01aba70c6546bdba6a9657&token=96044853&lang=zh_CN#rd) + +![fescar](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/fescar.png) + +### 4. mall + +- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall) +- **star**: 5.6 k +- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。 + +![mall](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/mall.png) + +**概览:** + +![mall](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/19-1-1/99819963.jpg) + +### 5. miaosha + +- **Github地址**:[https://github.com/qiurunze123/miaosha](https://github.com/qiurunze123/miaosha) +- **star**: 4.4k +- **介绍**: 高并发大流量如何进行秒杀架构,我对这部分知识做了一个系统的整理,写了一套系统。 + +![miaosha](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/miaosha.png) + +### 6. flink + +- **Github地址**:[https://github.com/apache/flink](https://github.com/apache/flink) +- **star**: 7.1 k +- **介绍**: Apache Flink是一个开源流处理框架,具有强大的流和批处理功能。 + +![flink](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/flink.png) + +关于Flink 更加详细的介绍可以查看这篇文章:https://www.cnblogs.com/feiyudemeng/p/8998772.html + +### 7. cim + +- **Github地址**:[https://github.com/crossoverJie/cim](https://github.com/crossoverJie/cim) +- **star**: 1.8 k +- **介绍**: cim(cross IM) 适用于开发者的即时通讯系统。 + +![cim](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/cim.png) + +**系统架构:** + +![cim系统架构](https://camo.githubusercontent.com/16f644ac7e2ab8cf8b8784408b1c70baf15634f4/68747470733a2f2f7773312e73696e61696d672e636e2f6c617267652f303036744e6252776c793166796c646769697a68756a3331356f3072346e306b2e6a7067) + +### 8. symphony + +- **Github地址**:[https://github.com/b3log/symphony](https://github.com/b3log/symphony) +- **star**: 10k +- **介绍**: 一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)平台。 + +![symphony](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/symphony.png) + +### 9. spring-boot + +- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot) +- **star:** 32.6k +- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。 + + **关于Spring Boot官方的介绍:** + + > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可) + +![spring-boot](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/spring-boot.png) + +### 10. arthas + +- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas) +- **star**: 9.5k +- **介绍**: Arthas 是Alibaba开源的Java诊断工具。 + +![arthas](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/arthas.png) + +**概览:** + +当你遇到以下类似问题而束手无策时,`Arthas`可以帮助你解决: + +0. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? +1. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? +2. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? +3. 线上遇到某个用户的数据处理有问题,但线上同样无法 debug,线下无法重现! +4. 是否有一个全局视角来查看系统的运行状况? +5. 有什么办法可以监控到JVM的实时运行状态? + +`Arthas`支持JDK 6+,支持Linux/Mac/Winodws,采用命令行交互模式,同时提供丰富的 `Tab` 自动补全功能,进一步方便进行问题的定位和诊断。 \ No newline at end of file diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/2019-2.md" "b/\351\227\262\350\260\210/JavaGithubTrending/2019-2.md" new file mode 100644 index 00000000000..51d34b32f78 --- /dev/null +++ "b/\351\227\262\350\260\210/JavaGithubTrending/2019-2.md" @@ -0,0 +1,64 @@ +### 1. JavaGuide + +- **Github地址**: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide) +- **Star**: 27.2k (4,437 stars this month) +- **介绍**: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。 + +### 2.DoraemonKit + +- **Github地址**: +- **Star**: 5.2k (3,786 stars this month) +- **介绍**: 简称 "DoKit" 。一款功能齐全的客户端( iOS 、Android )研发助手,你值得拥有。 + +### 3.advanced-java + +- **Github地址**:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java) +- **Star**:11.2k (3,042 stars this month) +- **介绍**: 互联网 Java 工程师进阶知识完全扫盲。 + +### 4. spring-boot-examples + +- **Github地址**: +- **star**: 9.6 k (1,764 stars this month) +- **介绍**: Spring Boot 教程、技术栈示例代码,快速简单上手教程。 + +### 5. mall + +- **Github地址**: [https://github.com/macrozheng/mall](https://github.com/macrozheng/mall) +- **star**: 7.4 k (1,736 stars this month) +- **介绍**: mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现。 前台商城系统包含首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统包含商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。 + +### 6. fescar + +- **Github地址**:[https://github.com/alibaba/fescar](https://github.com/alibaba/fescar) +- **star**: 6.0 k (1,308 stars this month) +- **介绍**: 具有 **高性能** 和 **易用性** 的 **微服务架构** 的 **分布式事务** 的解决方案。(特点:高性能且易于使用,旨在实现简单并快速的事务提交与回滚。) + +### 7. h4cker + +- **Github地址**: +- **star**: 2.1 k (1,303 stars this month) +- **介绍**: 该仓库主要由Omar Santos维护,包括与道德黑客/渗透测试,数字取证和事件响应(DFIR),漏洞研究,漏洞利用开发,逆向工程等相关的资源。 + +### 8. spring-boot + +- **Github地址**: [https://github.com/spring-projects/spring-boot](https://github.com/spring-projects/spring-boot) +- **star:** 34.8k (1,073 stars this month) +- **介绍**: 虽然Spring的组件代码是轻量级的,但它的配置却是重量级的(需要大量XML配置),不过Spring Boot 让这一切成为了过去。 另外Spring Cloud也是基于Spring Boot构建的,我个人非常有必要学习一下。 + + **关于Spring Boot官方的介绍:** + + > Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”…Most Spring Boot applications need very little Spring configuration.(Spring Boot可以轻松创建独立的生产级基于Spring的应用程序,只要通过 “just run”(可能是run ‘Application’或java -jar 或 tomcat 或 maven插件run 或 shell脚本)便可以运行项目。大部分Spring Boot项目只需要少量的配置即可) + +### 9. arthas + +- **Github地址**:[https://github.com/alibaba/arthas](https://github.com/alibaba/arthas) +- **star**: 10.5 k (970 stars this month) +- **介绍**: Arthas 是Alibaba开源的Java诊断工具。 + +### 10. tutorials + +- **Github地址**:[https://github.com/eugenp/tutorials](https://github.com/eugenp/tutorials) +- **star**: 12.1 k (789 stars this month) +- **介绍**: 该项目是一系列小而专注的教程 - 每个教程都涵盖Java生态系统中单一且定义明确的开发领域。 当然,它们的重点是Spring Framework - Spring,Spring Boot和Spring Securiyt。 除了Spring之外,还有以下技术:核心Java,Jackson,HttpClient,Guava。 + diff --git "a/\351\227\262\350\260\210/JavaGithubTrending/JavaGithubTrending.md" "b/\351\227\262\350\260\210/JavaGithubTrending/JavaGithubTrending.md" new file mode 100644 index 00000000000..229f6d2bdef --- /dev/null +++ "b/\351\227\262\350\260\210/JavaGithubTrending/JavaGithubTrending.md" @@ -0,0 +1,4 @@ +- [2018 年 12 月](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/2018-12.md) +- [2019 年 1 月](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/2019-1.md) +- [2019 年 2 月](https://github.com/Snailclimb/JavaGuide/blob/master/闲谈/JavaGithubTrending/2019-2.md) + diff --git "a/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" "b/\351\227\262\350\260\210/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" similarity index 86% rename from "\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" rename to "\351\227\262\350\260\210/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" index f8cbf587ec5..f14ee33a211 100644 --- "a/\345\205\266\344\273\226/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" +++ "b/\351\227\262\350\260\210/\344\270\252\344\272\272\351\230\205\350\257\273\344\271\246\347\261\215\346\270\205\345\215\225.md" @@ -30,6 +30,9 @@ - :thumbsup: [《Java并发编程的艺术》](https://book.douban.com/subject/26591326/) 这本书不是很适合作为Java并发入门书籍,需要具备一定的JVM基础。我感觉有些东西讲的还是挺深入的,推荐阅读。 +- :thumbsup: [《实战Java高并发程序设计》](https://book.douban.com/subject/26663605/) + + 豆瓣评分 8.3 ,书的质量没的说,推荐大家好好看一下。 - [《Java程序员修炼之道》](https://book.douban.com/subject/24841235/) @@ -67,7 +70,15 @@ 很一般的书籍,我就是当做课后图书来阅读的。 +> ### 代码优化 + +- :thumbsup: [《重构_改善既有代码的设计》](https://book.douban.com/subject/4262627/) + + 豆瓣 9.1 分,重构书籍的开山鼻祖。 +> ### linux操作系统相关 +- :thumbsup:[<>](https://book.douban.com/subject/25900403/) :thumbsup: [<>](https://book.douban.com/subject/1500149/) + 对于理解linux操作系统原理非常有用,同时可以打好个人的基本功力,面试中很多公司也会问到linux知识。 > ### 课外书籍 《技术奇点》 :thumbsup:《追风筝的人》 :thumbsup:《穆斯林的葬礼》 :thumbsup:《三体》 《人工智能——李开复》 diff --git "a/\345\205\266\344\273\226/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" "b/\351\227\262\350\260\210/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" similarity index 100% rename from "\345\205\266\344\273\226/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" rename to "\351\227\262\350\260\210/\351\200\211\346\213\251\346\212\200\346\234\257\346\226\271\345\220\221\351\203\275\350\246\201\350\200\203\350\231\221\345\223\252\344\272\233\345\233\240\347\264\240.md" diff --git "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" "b/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" deleted file mode 100644 index 91c24d1a180..00000000000 --- "a/\351\235\242\350\257\225\345\277\205\345\244\207/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" +++ /dev/null @@ -1,80 +0,0 @@ -![程序员的简历之道](https://user-gold-cdn.xitu.io/2018/7/25/164cf5c26e88cbcd?w=1024&h=902&f=jpeg&s=94869) - -> 俗话说的好:“工欲善其事,必先利其器”。准备一份好的简历对于能不能找到一份好工作起到了至关重要的作用。 - - - -## 六 如何写自己的简历? - -一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 - -### 6.1 为什么说简历很重要? - -假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。 - -假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。 - -另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。 - -### 6.2 这3点你必须知道 - -1. **大部分应届生找工作的硬伤是没有工作经验或实习经历;** -2. **写在简历上的东西一定要慎重,这可能是面试官大量提问的地方;** -3. **将自己的项目经历完美的展示出来非常重要。** - -### 6.3 两大法则了解一下 -目前写简历的方式有两种普遍被认可,一种是 STAR, 一种是 FAB。 - -**STAR法则(Situation Task Action Result):** - -- **Situation:** 事情是在什么情况下发生; -- **Task::** 你是如何明确你的任务的; -- **Action:** 针对这样的情况分析,你采用了什么行动方式; -- **Result:** 结果怎样,在这样的情况下你学习到了什么。 - -**FAB 法则(Feature Advantage Benefit):** - -- **Feature:** 是什么; -- **Advantage:** 比别人好在哪些地方; -- **Benefit:** 如果雇佣你,招聘方会得到什么好处。 - -### 6.4 项目经历怎么写? -简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写: - -1. 对项目整体设计的一个感受 -2. 在这个项目中你负责了什么、做了什么、担任了什么角色 -3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用 -4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。 - -### 6.5 专业技能该怎么写? -先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写: - -- Dubbo:精通 -- Spring:精通 -- Docker:掌握 -- SOA分布式开发 :掌握 -- Spring Cloud:了解 - - -### 6.6 开源程序员简历模板分享 - -分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 -Github地址:[https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample) - -### 6.7 其他的一些小tips - -1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。 -2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。 -3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。 -4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。 -5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容 -6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。 -7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。 -8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。 - - -本文摘自我的Gitchat:[《从应届程序员角度分析如何备战大厂面试》](https://gitbook.cn/gitchat/activity/5b457a5df64d4d62e64a449a)。 - - - -