本文系掘金首发,禁止转载哦! 若是以为文章内容不错的话,欢迎为我转身,啊!不对,是给我一个赞!点赞以后会有惊喜哦!html
看本文以前,推荐给你们一个阿里云双11活动,真的很是很是很是推荐,对于新人阿里云真的是下血本了,建议阿里云新人必定必定必定不要错过。若是以为这单纯是广告的话,你能够直接跳过看正文。前端
阿里云双11最新活动(仅限阿里云新用户购买,老用户拉新用户能够得到返现红包,后续有机会平分百万红包),优惠力度很是很是很是大,另外加入拼团,后续还有机会平分100w红包!目前个人战队已经有12位新人了,如今是折上5折了也就是1折购买!!!。 划重点了: 1核2G云服务器1年仅需99.5元!!!1核2G云服务器3年仅需298.50元!!!一个月仅需8.2元 该折扣仅限新人!这是个人拼团团队地址:m.aliyun.com/act/team111… !java
写本文以前,其实我本身已经开源了一个 Java学习指南的文档,里面包含了一些基础知识和一些后端(偏 Java 方向)的知识。到目前为止收获了 6.1k star 以及 1.5 k fork,close 了 10个 pr 以及 10 个issue。开源只是为了让更多的人看到和参与进来,这样文档的正确性和质量才能很好的保证。毕竟,我我的能力、时间以及知识广度和深度有限,一份好的项目的诞生确定离不开和其余人的共同努力。linux
另外,我我的以为不论你是前端仍是后端(部份内容可能会偏 Java 方向一点)都能从本文中学到东西。git
本人技术水平有限,欢迎各位指正!写的很差的话,请多见谅!程序员
不管是校招仍是社招都避免不了各类面试、笔试,如何去准备这些东西就显得格外重要。不管是笔试仍是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是能够提早准备。 我其实特别不喜欢那种临近考试就提早背啊记啊各类题的行为,很是反对!我以为这种方法特别极端,并且在稍有一点经验的面试官面前是根本没有用的。建议你们仍是一步一个脚印踏踏实实地走。github
指挥若定以后,决胜千里以外!不打毫无准备的仗,我以为你们能够先从下面几个方面来准备面试:面试
“80%的offer掌握在20%的人手中” 这句话也不是不无道理的。决定你面试可否成功的因素中实力当然占有很大一部分比例,可是若是你的心态或者说运气很差的话,依然没法拿到满意的 offer。运气暂且不谈,就拿心态来讲,千万不要由于面试失败而气馁或者说怀疑本身的能力,面试失败以后多总结一下失败的缘由,后面你就会发现本身会愈来愈强大。redis
另外,你们要明确的很重要的几点是:算法
笔主能力有限,若是有不对的地方或者和你想法不一样的地方,敬请雅正、不舍赐教。
若是想了解个人更多信息,能够关注个人 Github 或者微信公众号:"Java面试通关手册"。
俗话说的好:“工欲善其事,必先利其器”。准备一份好的简历对于能不能找到一份好工做起到了相当重要的做用。
假如你是网申,你的简历必然会通过HR的筛选,一张简历HR可能也就花费10秒钟看一下,而后HR就会决定你这一关是Fail仍是Pass。
假如你是内推,若是你的简历没有什么优点的话,就算是内推你的人再用心,也无能为力。
另外,就算你经过了筛选,后面的面试中,面试官也会根据你的简从来判断你到底是否值得他花费不少时间去面试。
目前写简历的方式有两种广泛被承认,一种是 STAR, 一种是 FAB。
STAR法则(Situation Task Action Result):
FAB 法则(Feature Advantage Benefit):
简历上有一两个项目经历很正常,可是真正能把项目经历很好的展现给面试官的很是少。对于项目经历你们能够考虑从以下几点来写:
先问一下你本身会什么,而后看看你意向的公司须要什么。通常HR可能并不太懂技术,因此他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你能够花几天时间学习一下,而后在简历上能够写上本身了解这个技能。好比你能够这样写:
分享一个Github上开源的程序员简历模板。包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板 。 Github地址:github.com/geekcompany…
若是想学如何用 Markdown 写简历写一份高质量简历,请看这里:github.com/Snailclimb/…
下面列举几个常见问题的回答!
UDP 在传送数据以前不须要先创建链接,远地主机在收到 UDP 报文后,不须要给出任何确认。虽然 UDP 不提供可靠交付,但在某些状况下 UDP 确是一种最有效的工做方式(通常用于即时通讯),好比: QQ 语音、 QQ 视频 、直播等等
TCP 提供面向链接的服务。在传送数据以前必须先创建链接,数据传送结束后要释放链接。 TCP 不提供广播或多播服务。因为 TCP 要提供可靠的,面向链接的运输服务(TCP的可靠体如今TCP在传递数据以前,会有三次握手来创建链接,并且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开链接用来节约系统资源),这一难以免增长了许多开销,如确认,流量控制,计时器以及链接管理等。这不只使协议数据单元的首部增大不少,还要占用许多处理机资源。TCP 通常用于文件传输、发送和接收邮件、远程登陆等场景。
百度好像最喜欢问这个问题。
打开一个网页,整个过程会使用哪些协议
图片来源:《图解HTTP》
整体来讲分为如下几个过程:
具体能够参考下面这篇文章:
通常面试官会经过这样的问题来考察你对计算机网络知识体系的理解。
图片来源:《图解HTTP》
在HTTP/1.0中默认使用短链接。也就是说,客户端和服务器每进行一次HTTP操做,就创建一次链接,任务结束就中断链接。当客户端浏览器访问的某个HTML或其余类型的Web页中包含有其余的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会从新创建一个HTTP会话。
而从HTTP/1.1起,默认使用长链接,用以保持链接特性。使用长链接的HTTP协议,会在响应头加入这行代码:
Connection:keep-alive
复制代码
在使用长链接的状况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP链接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经创建的链接。Keep-Alive不会永久保持链接,它有一个保持时间,能够在不一样的服务器软件(如Apache)中设定这个时间。实现长链接须要客户端和服务端都支持长链接。
HTTP协议的长链接和短链接,实质上是TCP协议的长链接和短链接。
为了准确无误地把数据送达目标处,TCP协议采用了三次握手策略。
漫画图解:
图片来源:《图解HTTP》
简单示意图:
为何要三次握手?
三次握手的目的是创建可靠的通讯信道,说到通信,简单来讲就是数据的发送与接收,而三次握手最主要的目的就是双方确认本身与对方的发送与接收是正常的。
第一次握手:Client 什么都不能确认;Server 确认了对方发送正常
第二次握手:Client 确认了:本身发送、接收正常,对方发送、接收正常;Server 确认了:本身接收正常,对方发送正常
第三次握手:Client 确认了:本身发送、接收正常,对方发送、接收正常;Server 确认了:本身发送、接收正常,对方发送接收正常
因此三次握手就能确认双发收发功能都正常,缺一不可。
为何要传回 SYN
接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
SYN 是 TCP/IP 创建链接时使用的握手信号。在客户机和服务器之间创建正常的 TCP 网络链接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通讯传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能创建起可靠的TCP链接,数据才能够在客户机和服务器之间传递。
传了 SYN,为啥还要传 ACK
双方通讯无误必须是二者互相发送信息都无误。传了 SYN,证实发送方到接收方的通道没有问题,可是接收方到发送方的通道还须要 ACK 信号来进行验证。
断开一个 TCP 链接则须要“四次挥手”:
为何要四次挥手
任何一方均可以在数据传送结束后发出链接释放的通知,待对方确认后进入半关闭状态。当另外一方也没有数据再发送的时候,则发出链接释放通知,对方确认后就彻底关闭了TCP链接。
举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,可是 B 可能还会有要说的话,A 不能要求 B 跟着本身的节奏结束通话,因而 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
上面讲的比较归纳,推荐一篇讲的比较细致的文章:
Linux文件系统简介
在Linux操做系统中,全部被操做系统管理的资源,例如网络接口卡、磁盘驱动器、打印机、输入输出设备、普通文件或是目录都被看做是一个文件。
也就是说在LINUX系统中有一个重要的概念:一切都是文件。其实这是UNIX哲学的一个体现,而Linux是重写UNIX而来,因此这个概念也就传承了下来。在UNIX系统中,把一切资源都看做是文件,包括硬件设备。UNIX系统把每一个硬件都当作是一个文件,一般称为设备文件,这样用户就能够用读写文件的方式实现对硬件的访问。
文件类型与目录结构
Linux支持5种文件类型 :
Linux的目录结构以下:
Linux文件系统的结构层次鲜明,就像一棵倒立的树,最顶层是其根目录:
常见目录说明:
目录切换命令
cd usr
: 切换到该目录下usr目录cd ..(或cd../)
: 切换到上一层目录cd /
: 切换到系统根目录cd ~
: 切换到用户主目录cd -
: 切换到上一个所在目录目录的操做命令(增删改查)
mkdir 目录名称
: 增长目录
ls或者ll
(ll是ls -l的缩写,ll命令以看到该目录下的全部目录和文件的详细信息):查看目录信息
find 目录 参数
: 寻找目录(查)
mv 目录名称 新目录名称
: 修改目录的名称(改)
注意:mv的语法不只能够对目录进行重命名并且也能够对各类文件,压缩包等进行 重命名的操做。mv命令用来对文件或目录从新命名,或者将文件从一个目录移到另外一个目录中。后面会介绍到mv命令的另外一个用法。
mv 目录名称 目录的新位置
: 移动目录的位置---剪切(改)
注意:mv语法不只能够对目录进行剪切操做,对文件和压缩包等均可执行剪切操做。另外mv与cp的结果不一样,mv好像文件“搬家”,文件个数并未增长。而cp对文件进行复制,文件个数增长了。
cp -r 目录名称 目录拷贝的目标位置
: 拷贝目录(改),-r表明递归拷贝
注意:cp命令不只能够拷贝目录还能够拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归
rm [-rf] 目录
: 删除目录(删)
注意:rm不只能够删除目录,也能够删除其余文件或压缩包,为了加强你们的记忆, 不管删除任何目录或文件,都直接使用rm -rf
目录/文件/压缩包
文件的操做命令(增删改查)
touch 文件名称
: 文件的建立(增)
cat/more/less/tail 文件名称
文件的查看(查)
cat
: 只能显示最后一屏内容more
: 能够显示百分比,回车能够向下一行, 空格能够向下一页,q能够退出查看less
: 能够使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看tail-10
: 查看文件的后10行,Ctrl+C结束注意:命令 tail -f 文件 能够对某个文件进行动态监控,例如tomcat的日志文件, 会随着程序的运行,日志会变化,能够使用tail -f catalina-2016-11-11.log 监控 文 件的变化
vim 文件
: 修改文件的内容(改)
vim编辑器是Linux中的强大组件,是vi编辑器的增强版,vim编辑器的命令和快捷方式有不少,但此处不一一阐述,你们也无需研究的很透彻,使用vim编辑修改文件的方式基本会使用就能够了。
在实际开发中,使用vim编辑器主要做用就是修改配置文件,下面是通常步骤:
vim 文件------>进入文件----->命令模式------>按i进入编辑模式----->编辑文件 ------->按Esc进入底行模式----->输入:wq/q! (输入wq表明写入内容并退出,即保存;输入q!表明强制退出不保存。)
rm -rf 文件
: 删除文件(删)
同目录删除:熟记 rm -rf
文件 便可
压缩文件的操做命令
1)打包并压缩文件:
Linux中的打包文件通常是以.tar结尾的,压缩的命令通常是以.gz结尾的。
而通常状况下打包和压缩是一块儿进行的,打包并压缩后的文件的后缀名通常.tar.gz。 命令:tar -zcvf 打包压缩后的文件名 要打包压缩的文件
其中:
z:调用gzip压缩命令进行压缩
c:打包文件
v:显示运行过程
f:指定文件名
好比:加入test目录下有三个文件分别是 :aaa.txt bbb.txt ccc.txt,若是咱们要打包test目录并指定压缩后的压缩包名称为test.tar.gz能够使用命令:tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt
或:tar -zcvf test.tar.gz /test/
2)解压压缩包:
命令:tar [-xvf] 压缩文件
其中:x:表明解压
示例:
1 将/test下的test.tar.gz解压到当前目录下能够使用命令:tar -xvf test.tar.gz
2 将/test下的test.tar.gz解压到根目录/usr下:tar -xvf xxx.tar.gz -C /usr
(- C表明指定解压的位置)
其余经常使用命令
pwd
: 显示当前所在位置
grep 要搜索的字符串 要搜索的文件 --color
: 搜索命令,--color表明高亮显示
ps -ef
/ps aux
: 这两个命令都是查看当前系统正在运行进程,二者的区别是展现格式不一样。若是想要查看特定的进程能够使用这样的格式:ps aux|grep redis
(查看包括redis字符串的进程)
注意:若是直接用ps((Process Status))命令,会显示全部进程的状态,一般结合grep命令查看某进程的状态。
kill -9 进程的pid
: 杀死进程(-9 表示强制终止。)
先用ps查找进程,而后用kill杀掉
网络通讯命令:
shutdown
: shutdown -h now
: 指定如今当即关机;shutdown +5 "System will shutdown after 5 minutes"
:指定5分钟后关机,同时送出警告信息给登入用户。
reboot
: reboot
: 重开机。reboot -w
: 作个重开机的模拟(只有纪录并不会真的重开机)。
关于两者的对比与总结:
MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库作主从分离的状况下,常常选择MyISAM做为主库的存储引擎。 通常来讲,若是须要事务支持,而且有较高的并发读取频率(MyISAM的表锁的粒度太大,因此当该表写并发量较高时,要等待的查询就会不少了),InnoDB是不错的选择。若是你的数据量很大(MyISAM支持压缩特性能够减小磁盘的空间占用),并且不须要支持事务时,MyISAM是最好的选择。
Mysql索引使用的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来讲,底层的数据结构就是哈希表,所以在绝大多数需求为单条记录查询的时候,能够选择哈希索引,查询性能最快;其他大部分场景,建议选择BTree索引。
Mysql的BTree索引使用的是B数中的B+Tree,但对于主要的两种存储引擎的实现方式是不一样的。
另外,再推荐几篇比较好的关于索引的文章:
当MySQL单表记录数过大时,数据库的CRUD性能会明显降低,一些常见的优化措施以下:
限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。好比:咱们当用户在查询订单历史的时候,咱们能够控制在一个月的范围内。;
读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
缓存: 使用MySQL的缓存,另外对重量级、更新少的数据能够考虑使用应用级别的缓存;
垂直分区:
根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登陆信息又有用户的基本信息,能够将用户表拆分红两个单独的表,甚至放到单独的库作分库。
简单来讲垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 以下图所示,这样来讲你们应该就更容易理解了。
垂直拆分的优势: 能够使得行数据变小,在查询时减小读取的Block数,减小I/O次数。此外,垂直分区能够简化表的结构,易于维护。
垂直拆分的缺点: 主键会出现冗余,须要管理冗余列,并会引发Join操做,能够经过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
水平分区:
保持数据表结构不变,经过某种策略存储数据分片。这样每一片数据分散到不一样的表或者库中,达到了分布式的目的。 水平拆分能够支撑很是大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时能够把一张的表的数据拆成多张表来存放。举个例子:咱们能够将用户信息表拆分红多个用户信息表,这样就能够避免单一表数据量过大对性能形成影响。
水品拆分能够支持很是大的数据量。须要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但因为表的数据仍是在同一台机器上,其实对于提高MySQL并发能力没有什么意义,因此 水品拆分最好分库 。
水平拆分可以 支持很是大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。《Java工程师修炼之道》的做者推荐 尽可能不要对数据进行分片,由于拆分会带来逻辑、部署、运维的各类复杂度 ,通常的数据表在优化得当的状况下支撑千万如下的数据量是没有太大问题的。若是实在要分片,尽可能选择客户端分片架构,这样能够减小一次和中间件的网络I/O。
下面补充一下数据库分片的两种常见方案:
关于 redis 必知必会的11个问题!后两个问题,暂未更新!若有须要,能够关注个人 Github 或者微信公众号:“Java面试通关手册”获取后续更新内容。
简单来讲 redis 就是一个数据库,不过与传统数据库不一样的是 redis 的数据是存在内存中的,因此存写速度很是快,所以 redis 被普遍应用于缓存方向。另外,redis 也常常用来作分布式锁。redis 提供了多种数据类型来支持不一样的业务场景。除此以外,redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。
主要从“高性能”和“高并发”这两点来看待这个问题。
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,由于是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就能够直接从缓存中获取了。操做缓存就是直接操做内存,因此速度至关快。若是数据库中的对应数据改变的以后,同步改变缓存中相应的数据便可!
高并发:
直接操做缓存可以承受的请求是远远大于直接访问数据库的,因此咱们能够考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用通过数据库。
下面的内容来自 segmentfault 一位网友的提问,地址:segmentfault.com/q/101000000…
缓存分为本地缓存和分布式缓存。以java为例,使用自带的map或者guava实现的是本地缓存,最主要的特色是轻量以及快速,生命周期随着 jvm 的销毁而结束,而且在多实例的状况下,每一个实例都须要各自保存一份缓存,缓存不具备一致性。
使用 redis 或 memcached 之类的称为分布式缓存,在多实例的状况下,各实例共用一份缓存数据,缓存具备一致性。缺点是须要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
对于 redis 和 memcached 我总结了下面四点。如今公司通常都是用 redis 来实现缓存,并且 redis 自身也愈来愈强大了!
来自网络上的一张图,这里分享给你们!
经常使用命令: set,get,decr,incr,mget 等。
String数据结构是简单的key-value类型,value其实不只能够是String,也能够是数字。 常规key-value缓存应用; 常规计数:微博数,粉丝数等。
经常使用命令: hget,hset,hgetall 等。
Hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,后续操做的时候,你能够直接仅仅修改这个对象中的某个字段的值。 好比咱们能够Hash数据结构来存储用户信息,商品信息等等。好比下面我就用 hash 类型存放了我本人的一些信息:
key=JavaUser293847
value={
“id”: 1,
“name”: “SnailClimb”,
“age”: 22,
“location”: “Wuhan, Hubei”
}
复制代码
经常使用命令: lpush,rpush,lpop,rpop,lrange等
list就是链表,Redis list的应用场景很是多,也是Redis最重要的数据结构之一,好比微博的关注列表,粉丝列表,消息列表等功能均可以用Redis的 list 结构来实现。
Redis list 的实现为一个双向链表,便可以支持反向查找和遍历,更方便操做,不过带来了部分额外的内存开销。
另外能够经过 lrange 命令,就是从某个元素开始读取多少个元素,能够基于 list 实现分页查询,这个很棒的一个功能,基于 redis 实现简单的高性能分页,能够作相似微博那种下拉不断分页的东西(一页一页的往下走),性能高。
经常使用命令: sadd,spop,smembers,sunion 等
set对外提供的功能与list相似是一个列表的功能,特殊之处在于set是能够自动排重的。
当你须要存储一个列表数据,又不但愿出现重复数据时,set是一个很好的选择,而且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。能够基于 set 轻易实现交集、并集、差集的操做。
好比:在微博应用中,能够将一个用户全部的关注人存在一个集合中,将其全部粉丝存在一个集合。Redis能够很是方便的实现如共同关注、共同粉丝、共同喜爱等功能。这个过程也就是求交集的过程,具体命令以下:
sinterstore key1 key2 key3 将交集存在key1内
复制代码
经常使用命令: zadd,zrange,zrem,zcard等
和set相比,sorted set增长了一个权重参数score,使得集合中的元素可以按score进行有序排列。
举例: 在直播系统中,实时排行信息包含直播间在线用户列表,各类礼物排行榜,弹幕消息(能够理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 SortedSet 结构进行存储。
Redis中有个设置时间过时的功能,即对存储在 redis 数据库中的值能够设置一个过时时间。做为一个缓存数据库,这是很是实用的。如咱们通常项目中的token或者一些登陆信息,尤为是短信验证码都是有时间限制的,按照传统的数据库处理方式,通常都是本身判断过时,这样无疑会严重影响项目性能。
咱们set key的时候,均可以给一个expire time,就是过时时间,经过过时时间咱们能够指定这个 key 能够存货的时间。
若是假设你设置一个一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?
按期删除+惰性删除。
经过名字大概就能猜出这两个删除方式的意思了。
可是仅仅经过设置过时时间仍是有问题的。咱们想一下:若是按期删除漏掉了不少过时 key,而后你也没及时去查,也就没走惰性删除,此时会怎么样?若是大量过时key堆积在内存里,致使redis内存块耗尽了。怎么解决这个问题呢?
redis 内存淘汰机制。
redis 配置文件 redis.conf 中有相关注释,我这里就不贴了,你们能够自行查阅或者经过这个网址查看: download.redis.io/redis-stabl…
redis 提供 6种数据淘汰策略:
备注: 关于 redis 设置过时时间以及内存淘汰机制,我这里只是简单的总结一下,后面会专门写一篇文章来总结!
不少时候咱们须要持久化数据也就是将内存中的数据写入到硬盘里面,大部分缘由是为了以后重用数据(好比重启机器、机器故障以后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis不一样于Memcached的很重一点就是,Redis支持持久化,并且支持两种不一样的持久化操做。Redis的一种持久化方式叫快照(snapshotting,RDB),另外一种方式是只追加文件(append-only file,AOF).这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合本身的持久化方法。
快照(snapshotting)持久化(RDB)
Redis能够经过建立快照来得到存储在内存里面的数据在某个时间点上的副本。Redis建立快照以后,能够对快照进行备份,能够将快照复制到其余服务器从而建立具备相同数据的服务器副本(Redis主从结构,主要用来提升Redis性能),还能够将快照留在原地以便重启服务器的时候使用。
快照持久化是Redis默认采用的持久化方式,在redis.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参数开启:
appendonly yes
复制代码
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是经过dir参数设置的,默认的文件名是appendonly.aof。
在Redis的配置文件中存在三种不一样的 AOF 持久化方式,它们分别是:
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重下降Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操做系统决定什么时候进行同步
复制代码
为了兼顾数据和写入性能,用户能够考虑 appendfsync everysec选项 ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。并且这样即便出现系统崩溃,用户最多只会丢失一秒以内产生的数据。当硬盘忙于执行写入操做的时候,Redis还会优雅的放慢本身的速度以便适应硬盘的最大写入速度。
补充内容:AOF 重写
AOF重写能够产生一个新的AOF文件,这个新的AOF文件和原有的AOF文件所保存的数据库状态同样,但体积更小。
AOF重写是一个有歧义的名字,该功能是经过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任伺读入、分析或者写人操做。
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程建立新AOF文件期间,记录服务器执行的全部写命令。当子进程完成建立新AOF文件的工做以后,服务器会将重写缓冲区中的全部内容追加到新AOF文件的末尾,使得新旧两个AOF文件所保存的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操做
更多内容能够查看个人这篇文章:
缓存雪崩
简介:缓存同一时间大面积的失效,因此,后面的请求都会落到数据库上,形成数据库短期内承受大量请求而崩掉。
解决办法(中华石杉老师在他的视频中提到过):
缓存穿透
简介:通常是黑客故意去请求缓存中不存在的数据,致使全部的请求都落到数据库上,形成数据库短期内承受大量请求而崩掉。
解决办法: 有不少种方法能够有效地解决缓存穿透问题,最多见的则是采用布隆过滤器,将全部可能存在的数据哈希到一个足够大的bitmap中,一个必定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法(咱们采用的就是这种),若是一个查询返回的数据为空(无论是数 据不存在,仍是系统故障),咱们仍然把这个空结果进行缓存,但它的过时时间会很短,最长不超过五分钟。
参考:
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操做,可是最后执行的顺序和咱们指望的顺序不一样,这样也就致使告终果的不一样!
推荐一种方案:分布式锁(zookeeper 和 redis 均可以实现分布式锁)。(若是不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)
基于zookeeper临时有序节点能够实现的分布式锁。大体思想为:每一个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个惟一的瞬时有序节点。 判断是否获取锁的方式很简单,只须要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除便可。同时,其能够避免服务宕机致使的锁没法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
在实践中,固然是从以可靠性为主。因此首推Zookeeper。
参考:
你只要用缓存,就可能会涉及到缓存与数据库双存储双写,你只要是双写,就必定会有数据一致性的问题,那么你如何解决一致性问题?
通常来讲,就是若是你的系统不是严格要求缓存+数据库必须一致性的话,缓存能够稍微的跟数据库偶尔有不一致的状况,最好不要作这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就能够保证必定不会出现不一致的状况
串行化以后,就会致使系统的吞吐量会大幅度的下降,用比正常状况下多几倍的机器去支撑线上的一个请求。
参考:
重载和重写的区别
重载: 发生在同一个类中,方法名必须相同,参数类型不一样、个数不一样、顺序不一样,方法返回值和访问修饰符能够不一样,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;若是父类方法访问修饰符为 private 则子类就不能重写该方法。
String 和 StringBuffer、StringBuilder 的区别是什么?String 为何是不可变的?
可变性
简单的来讲:String 类中使用 final 关键字字符数组保存字符串,private final char value[]
,因此 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value
可是没有用 final 关键字修饰,因此这两种对象都是可变的。
StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,你们能够自行查阅源码。
AbstractStringBuilder.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 对象自己进行操做,而不是生成新的对象并改变对象引用。相同状况下使用 StirngBuilder 相比使用 StringBuffer 仅能得到 10%~15% 左右的性能提高,但却要冒多线程不安全的风险。
对于三者使用的总结:
自动装箱与拆箱
装箱:将基本类型用它们对应的引用类型包装起来;
拆箱:将包装类型转换为基本数据类型;
== 与 equals
== : 它的做用是判断两个对象的地址是否是相等。即,判断两个对象是否是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
equals() : 它的做用也是判断两个对象是否相等。但它通常有两种使用状况:
举个例子:
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");
}
}
}
复制代码
说明:
关于 final 关键字的一些总结
final关键字主要用在三个地方:变量、方法、类。
Arraylist 与 LinkedList 异同
add(E e)
方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种状况时间复杂度就是O(1)。可是若是要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。由于在进行上述操做的时候集合中第 i 和第 i 个元素以后的(n-i)个元素都要执行向后位/向前移一位的操做。 ② LinkedList 采用链表存储,因此插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。get(int index)
方法)。补充:数据结构基础之双向链表
双向链表也叫双链表,是链表的一种,它的每一个数据结点中都有两个指针,分别指向直接后继和直接前驱。因此,从双向链表中的任意一个结点开始,均可以很方便地访问它的前驱结点和后继结点。通常咱们都构造双向循环链表,以下图所示,同时下图也是LinkedList 底层使用的是双向循环链表数据结构。
ArrayList 与 Vector 区别
Vector类的全部方法都是同步的。能够由两个线程安全地访问一个Vector对象、可是一个线程访问Vector的话代码要在同步操做上耗费大量的时间。
Arraylist不是同步的,因此在不须要保证线程安全时时建议使用Arraylist。
HashMap的底层实现
①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 方法更加简化,可是原理不变。
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 方法源码.
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以后
相比于以前的版本, JDK1.8以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减小搜索时间。
TreeMap、TreeSet以及JDK1.8以后的HashMap底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,由于二叉查找树在某些状况下会退化成一个线性结构。
推荐阅读:
HashMap 和 Hashtable 的区别
synchronized
修饰。(若是你要保证线程安全的话就使用 ConcurrentHashMap 吧!);HashMap 的长度为何是2的幂次方
为了能让 HashMap 存取高效,尽可能较少碰撞,也就是要尽可能把数据分配均匀。咱们上面也讲到了过了,Hash 值的范围值-2147483648到2147483648,先后加起来大概40亿的映射空间,只要哈希函数映射得比较均匀松散,通常应用是很难出现碰撞的。但问题是一个40亿长度的数组,内存是放不下的。因此这个散列值是不能直接拿来用的。用以前还要先作对数组的长度取模运算,获得的余数才能用来要存放的位置也就是对应的数组下标。
这个算法应该如何设计呢?
咱们首先可能会想到采用%取余的操做来实现。可是,重点来了:“取余(%)操做中若是除数是2的幂次则等价于与其除数减一的与(&)操做(也就是说 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 而且 采用二进制位操做 &,相对于%可以提升运算效率,这就解释了 HashMap 的长度为何是2的幂次方。
HashMap 多线程操做致使死循环问题
在多线程下,进行 put 操做会致使 HashMap 死循环,缘由在于 HashMap 的扩容 resize()方法。因为扩容是新建一个数组,复制原数据到数组。因为数组下标挂有链表,因此须要复制链表,可是多线程操做有可能致使环形链表。复制链表过程以下:
如下模拟2个线程同时扩容。假设,当前 HashMap 的空间为2(临界值为1),hashcode 分别为 0 和 1,在散列地址 0 处有元素 A 和 B,这时候要添加元素 C,C 通过 hash 运算,获得散列地址为 1,这时候因为超过了临界值,空间不够,须要调用 resize 方法进行扩容,那么在多线程条件下,会出现条件竞争,模拟过程以下:
线程一:读取到当前的 HashMap 状况,在准备扩容时,线程二介入
线程二:读取 HashMap,进行扩容
线程一:继续执行
这个过程为,先将 A 复制到新的 hash 表中,而后接着复制 B 到链头(A 的前边:B.next=A),原本 B.next=null,到此也就结束了(跟线程二同样的过程),可是,因为线程二扩容的缘由,将 B.next=A,因此,这里继续复制A,让 A.next=B,由此,环形链表出现:B.next=A; A.next=B
HashSet 和 HashMap 区别
若是你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码很是很是少,由于除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 本身不得不实现以外,其余方法都是直接调用 HashMap 中的方法。)
ConcurrentHashMap 和 Hashtable 的区别
ConcurrentHashMap 和 Hashtable 的区别主要体如今实现线程安全的方式上不一样。
二者的对比图:
图片来源:www.cnblogs.com/chengxiao/p…
HashTable:
JDK1.7的ConcurrentHashMap:
ConcurrentHashMap线程安全的具体实现方式/底层具体实现
①JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,而后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其余段的数据也能被其余线程访问。
ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成。
Segment 实现了 ReentrantLock,因此 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
static class Segment<K,V> 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倍。
集合框架底层数据结构总结
Collection
1.List
Arraylist: Object数组
Vector: Object数组
LinkedList: 双向循环链表 2.Set
HashSet(无序,惟一): 基于 HashMap 实现的,底层采用 HashMap 来保存元素
LinkedHashSet: LinkedHashSet 继承与 HashSet,而且其内部是经过 LinkedHashMap 来实现的。有点相似于咱们以前说的LinkedHashMap 其内部是基于 Hashmap 实现同样,不过仍是有一点点区别的。
TreeSet(有序,惟一): 红黑树(自平衡的排序二叉树。)
Map
关于 Java多线程,在面试的时候,问的比较多的就是①悲观锁和乐观锁( 具体能够看个人这篇文章:面试必备之乐观锁与悲观锁)、②synchronized和lock区别以及volatile和synchronized的区别,③可重入锁与非可重入锁的区别、④多线程是解决什么问题的、⑤线程池解决什么问题、⑥线程池的原理、⑦线程池使用时的注意事项、⑧AQS原理、⑨ReentranLock源码,设计原理,总体过程 等等问题。
面试官在多线程这一部分极可能会问你有没有在项目中实际使用多线程的经历。因此,若是你在你的项目中有实际使用Java多线程的经历 的话,会为你加分很多哦!
关于Java虚拟机,在面试的时候通常会问的大多就是①Java内存区域、②虚拟机垃圾算法、③虚拟机垃圾收集器、④JVM内存管理、⑤JVM调优这些问题了。 具体能够查看个人这两篇文章:
设计模式比较常见的就是让你手写一个单例模式(注意单例模式的几种不一样的实现方法)或者让你说一下某个常见的设计模式在你的项目中是如何使用的,另外面试官还有可能问你抽象工厂和工厂方法模式的区别、工厂模式的思想这样的问题。
建议把代理模式、观察者模式、(抽象)工厂模式好好看一下,这三个设计模式也很重要。
数据结构比较常问的就是:二叉树、红黑树(极可能让你手绘一个红黑树出来哦!)、二叉查找树(BST)、平衡二叉树(Self-balancing binary search tree)、B-树,B+树与B*树的优缺点比较、 LSM 树这些知识点。
数据结构很重要,并且学起来也相对要难一些。建议学习数据结构必定要按部就班的来,一步一个脚印的走好。必定要搞懂原理,最好本身能用代码实现一遍。
常见的加密算法、排序算法都须要本身提早了解一下,排序算法最好本身可以独立手写出来。
我以为面试中最刺激、最有压力或者说最有挑战的一个环节就是手撕算法了。面试中大部分算法题目都是来自于Leetcode、剑指offer上面,建议你们能够天天挤出一点时间刷一下算法题。
推荐两个刷题必备网站:
LeetCode:
牛客网:
面试官可能会问你,了解哪些排序方法啊?除了冒泡排序和选择排序能不能给我手写一个其余的排序算法。或者面试官可能会直接问你:“能不能给我手写一个快排出来?”。
快排的基本思想: 经过选择的参考值将待排序记录分割成独立的两部分,一部分全小于选取的参考值,另外一部分全大于选取的参考值。对分割以后的部分再进行一样的操做直到没法再进行该操做位置(能够使用递归)。
下面是我写的一个简单的快排算法,我选择的参考值是数组的第一个元素。
import java.util.Arrays;
public class QuickSort {
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] num = { 1, 3, 4, 8, 5, 10, 22, 15, 16 };
QuickSort.quickSort(num, 0, num.length - 1);
System.out.println(Arrays.toString(num));
}
public static void quickSort(int[] a, int start, int end) {
// 该值定义了从哪一个位置开始分割数组
int ref;
if (start < end) {
// 调用partition方法对数组进行排序
ref = partition(a, start, end);
// 对分割以后的两个数组继续进行排序
quickSort(a, start, ref - 1);
quickSort(a, ref + 1, end);
}
}
/** * 选定参考值对给定数组进行一趟快速排序 * * @param a * 数组 * @param start * (切分)每一个数组的第一个的元素的位置 * @param end * (切分)每一个数组的最后一个的元素位置 * @return 下一次要切割数组的位置 */
public static int partition(int[] a, int start, int end) {
// 取数组的第一个值做为参考值(关键数据)
int refvalue = a[start];
// 从数组的右边开始往左遍历,直到找到小于参考值的元素
while (start < end) {
while (end > start && a[end] >= refvalue) {
end--;
}
// 将元素直接赋予给左边第一个元素,即pivotkey所在的位置
a[start] = a[end];
// 从序列的左边边开始往右遍历,直到找到大于基准值的元素
while (end > start && a[start] <= refvalue) {
start++;
}
a[end] = a[start];
return end;
}
// 最后的start是基准值所在的位置
a[start] = refvalue;
return start;
}
}
复制代码
时间复杂度分析:
空间复杂度分析:
一种简单优化的方式:
三向切分快速排序 :核心思想就是将待排序的数据分为三部分,左边都小于比较值,右边都大于比较值,中间的数和比较值相等.三向切分快速排序的特性就是遇到和比较值相同时,不进行数据交换, 这样对于有大量重复数据的排序时,三向切分快速排序算法就会优于普通快速排序算法,但因为它总体判断代码比普通快速排序多一点,因此对于常见的大量非重复数据,它并不能比普通快速排序多大多的优点 。
Spring通常是不可避免的,若是你的简历上注明了你会Spring Boot或者Spring Cloud的话,那么面试官也可能会同时问你这两个技术,好比他可能会问你springboot和spring的区别。 因此,必定要谨慎对待写在简历上的东西,必定要对简历上的东西很是熟悉。
另外,AOP实现原理、动态代理和静态代理、Spring IOC的初始化过程、IOC原理、本身怎么实现一个IOC容器? 这些东西都是常常会被问到的。
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
支持当前事务的状况:
不支持当前事务的状况:
其余状况:
AOP思想的实现通常都是基于 代理模式 ,在JAVA中通常采用JDK动态代理模式,可是咱们都知道,JDK动态代理模式只能代理接口而不能代理类。所以,Spring AOP 会这样子来进行切换,由于Spring AOP 同时支持 CGLIB、ASPECTJ、JDK动态代理。
这部份内容能够查看下面这几篇文章:
Spring IOC的初始化过程:
IOC源码阅读
我以为实际场景题就是对你的知识运用能力以及思惟能力的考察。建议你们在平时养成多思考问题的习惯,这样面试的时候碰到这样的问题就不至于慌了。另外,若是本身实在不会就给面试官委婉的说一下,面试官可能会给你提醒一下。切忌不懂装懂,乱答一气。 面试官可能会问你相似这样的问题:①假设你要作一个银行app,有可能碰到多我的同时向一个帐户打钱的状况,有可能碰到什么问题,如何解决(锁)②你是怎么保证你的代码质量和正确性的?③下单过程当中是下订单减库存仍是付款减库存,分析一下二者的优劣;④同时给10万我的发工资,怎么样设计并发方案,能确保在1分钟内所有发完。⑤若是让你设计xxx系统的话,你会如何设计。
最后,再强调几点:
另外,我我的以为面试也像是一场全新的征程,失败和胜利都是日常之事。因此,劝各位不要由于面试失败而灰心、丧失斗志。也不要由于面试经过而沾沾自喜,等待你的将是更美好的将来,继续加油!
初次以外,笔主也在这里给本身挖一个坑,关于 dubbo、zookeeper 等内容我会在后续作一个系统总结。保证你们看了以后,必定有收获!
最后,附上征文连接。
还有三个本次活动的合做伙伴: