目录
1. 计算机网络
(1)网络7层架构
(2)TCP/IP原理
(3)HTTP原理
(4)加密算法
2. 数据结构
3. 算法
(1) Java算法
(2)海量数据处理
4. 操作系统
5. MySQL数据库
1、事务
2、数据库结构和锁
3、索引
4、情景题、优化题
5、琐碎知识
6、视图
7、存储过程和触发器
8、约束
6. Redis数据库
7. Git
8. Linux
9. 位运算技巧
二、Java
1. Java基础
2. JVM
3. Java并发编程
1、互斥(无锁和互斥锁)
2、线程
3、协作
4、分工
4. Java集合框架
5. Servlet
三、设计模式
1. 单例模式
2. 工厂模式—— 创建对象的创建型模式
3. 适配器模式 —— 两个不兼容的接口之间的桥梁,结构型模式
4. 装饰器模式 —— 赋予现有的类更多的功能,结构型模式
5. 观察者模式 —— 行为型模式
6. 责任链模式 —— 行为型模式
1. 说一说OSI七层模型
第七层:应用层——为应用程序提供服务,最小单位——apdu;
第六层:表示层——确保一个系统的应用层发送的消息可以 被另一个系统的应用层读取,加密解密、转换翻译、压缩解压缩;最小单位——ppdu;
第五层:会话层——不同的机器上的用户之间建立、管理、维护对话;
第四层:传输层——定义一些传输数据的协议和端口。传输协议同时进行流量控制,或是拥塞控制,解决传输效率与能力的问题,保证这些数据段有效到达对端;最小单位——tpdu;
第三层:网络层——控制子网的运行,如逻辑编址、分组传输、路由选择;最小单位——分组(包)报文;
第二层:数据链路层——物理寻址,同时将原始比特流转变为逻辑传输线路;最小单位——帧;
第一层:物理层——定义物理设备的标准,主要对物理连接方式,电气特性,机械特性等制定统一标准,传输比特流;最小单位——比特流。
2. 说一说TCP/IP四层协议
3. 简述TCP/UDP的区别
TCP和UDP是OSI模型中的传输层中的协议,TCP提供可靠的通信传输,UDP提供广播和细节控制交给应用层的通信传输
TCP面向连接 | UDP发送数据前不需要建立连接,面向的是非连接 |
TCP提供可靠的服务(数据传输) | UDP无法保证数据传输的可靠性 |
TCP面向字节流 | UDP面向报文 |
TCP数据传输慢 | UDP数据传输快 |
4. TCP的三次握手,如果在前两次握手后客户端不发起第三次握手会怎么样?(SYN Flood攻击的诊断和处理)
第一次握手:客户端发送建立连接的报文段,将SYN位置为1,Sequence Number为x;然后客户端进入SYN_SEND状态,等待服务器确认。
第二次握手:服务器收到客户端的SYN报文段,对其进行确认:设置Acknowledgement Number为x+1;同时还要发送SYN请求信息,将SYN为置为1,Sequence Number为y;将上述信息放到一个报文段中,一并发给客户端;然后服务器进入SYN_RECV状态。
第三次握手:客户端接收到SYN_ACK报文段,将Acknowledgement Number置为y+1,向服务器发送ACK报文段。这个报文段发送完毕后,客户端和服务器都进入ESTABLISHED状态,完成TCP三次握手。
【注意】
(1)在握手和结束时Acknowledgement Number确认号为对方的序列号加1;
(2)传输数据时则是对方序列号加上对方携带应用层数据的长度。
【SYN Flood攻击】
【SYN Flood防御】
【链接】---- TCP洪水攻击的诊断和处理
【链接】---- DDoS攻击--Syn_Flood攻击防护详解(TCP)
5. 如果握手只有两次,会出现什么情况?
如果握手只有两次,会发生已经失效的连接请求报文段被服务器接收,因为只有两次握手,服务器一收到连接请求就会进入ESTABLISHED状态,等待客户端发送数据或主动发送数据,而客户端其实是处于CLOSED状态,服务器会一直等待下去,这样会浪费服务器资源。
6. TCP的四次挥手,为什么TCP断开连接要4次
关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你未必会马上会关闭SOCKET,等你也没有数据再发送的时候,再发送FIN报文给对方来表示你同意现在关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
7. TIME_WAIT和CLOSE_WAIT的区别,大量CLOSE_WAIT怎么办?
【链接】---- 关于大量CLOSE_WAIT连接分析
TIME_WAIT 是主动关闭连接时形成的,等待2MSL时间,约2分钟。主要是防止最后一个ACK丢失。 由于TIME_WAIT 的时间会非常长,因此server端应尽量减少主动关闭连接。
CLOSE_WAIT是被动关闭连接是形成的。根据TCP状态机,服务器端收到客户端发送的FIN,则按照TCP实现发送ACK,因此进入CLOSE_WAIT状态。但如果服务器端不执行close(),就不能由CLOSE_WAIT迁移到LAST_ACK,则系统中会存在很多CLOSE_WAIT状态的连接。此时,可能是系统忙于处理读、写操作,而未将已收到FIN的连接,进行close。
【链接】------- TIME_WAIT和CLOSE_WAIT状态区别
8. TCP头的结构,如何标识一个TCP连接(四元组、五元组、七元组)
◆URG:紧急指针(urgent pointer)有效。
◆ACK:确认序号有效。
◆PSH:接收方应该尽快将这个报文段交给应用层。
◆RST:重建连接。
◆SYN:发起一个连接。
◆FIN:释放一个连接。
【四元组】源IP地址、目的IP地址、源端口、目的端口
【五元组】源IP地址、目的IP地址、协议号、源端口、目的端口
【七元组】源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型以及接口索引
9. TCP的拆包粘包问题,TCP的最大连接数,TCP攻击
【拆包粘包定义】
TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的流水,是连成一片的,其间并没有分界线。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
【解决方案】
【TCP的最大连接数】
【链接】---- 单服务器最大tcp连接数及调优
10. TCP/IP协议的可靠性,以及它的脆弱性。
【TCP/IP协议的可靠性】
【链接】------- TCP协议如何保证传输可靠性
【TCP/IP的脆弱性】
(1)不能提供可靠的身份验证
(2)不能有效防止信息泄露
(3)没有提供 ----可靠的信息完整性验证---- 手段
(4)没有手段控制资源占用和分配
11. TCP的流量控制和拥塞控制
【链接】---- TCP的流量控制和拥塞控制
【流量控制】流量控制是作用于发送者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失
【如何实现流量控制】由滑动窗口协议实现,主要的方法是接收方返回的ACK中包含自己的接收窗口大小,并且利用大小来控制发送方的数据发送。
【流量控制引发的死锁】如果发送者收到了一个窗口为0的应答,发送者会停止发送,等待接收者的下一次应答;但是如果接收者发送了一个窗口不为0的数据在传输过程中丢失了,发送者会一直等待下去,而接收者以为发送者已经收到了应答,等待接收新的数据,这样双方就相互等待,从而产生了死锁。
【避免死锁的发生】TCP为每一个连接设有一个持续计时器。每当发送者收到一个窗口为0的ACK应答后就启动该计时器,时间一到就主动询问接收者的窗口大小;如果接收者仍然返回零窗口,则重置该计时器继续等待;如果窗口不为0,则表明应答报文丢失了,发送方重置发送窗口后开始发送,这样就避免了死锁的产生。
【拥塞控制】拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况
常用的方法是:(1)慢开始、拥塞避免;(2)快重传、快恢复
【慢开始】不要一开始就发送大量的数据,先探测一下网络的拥塞程度,即由大到小逐渐增加拥塞窗口的大小。每经历一个传输轮次,拥塞窗口cwnd就加倍。为防止cwnd增长过大引起网络拥塞,设置了一个慢开始门限ssthresh。
【拥塞避免】拥塞避免算法让拥塞窗口缓慢增长,每经历一个传输轮次,cwnd加1,而不是加倍,这样按线性缓慢增长。
无论是在慢开始阶段还是拥塞避免阶段,只要发送方判断网络出现拥塞,就会将慢开始门限ssthresh设置为网络拥塞时的发送窗口的 一半,拥塞窗口cwnd重新置为1,执行慢开始算法。
【快重传】快重传要求接收方一收到失序的报文段就立即发出重复确认,而不要等到自己发送数据的时候捎带确认。快重传算法规定,发送方一连收到三个重复确认就应答立即重传尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。这样的做法是及早让发送方知道报文段没有到达接收方,可提高网络吞吐量约20%。
【快恢复】快重传和快恢复配合使用,体现为:当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半(为了防止网络发生拥塞)。然后将cwnd设置为ssthresh减半以后的值,执行拥塞避免算法,使cwnd缓慢增长。
12. Socket套接字——是对传输层的TCP/IP协议的封装,是一组接口
区分不同应用程序进程间的网络通信和连接,主要有三个参数:目的IP地址,端口号,使用的传输层协议(TCP和UDP)
13. 常用的端口号及对应的服务
端口 | 服务 |
21 | FTP文件传输协议 |
23 | Telnet远程登录服务 |
25 | SMTP简单邮件传输协议 |
53 | DNS协议,其运行在UDP协议之上 |
80 | HTTP超文本传输协议 |
443 | https |
3306 | MySQL |
16. 当你用浏览器打开一个链接(如:http://www.javastack.cn)的时候,计算机做了哪些工作步骤。
— —计算机的工作步骤:
1.URL解析,从链接地址中分解出协议名、主机名、端口、资源路径、动态参数等部分;
2.DNS域名解析,查找域名对应的IP地址,这一步会依次查找浏览器缓存,系统缓存,路由器缓存,根域名服务器;
3.然后利用TCP/P协议开始根据分层顺序与对方进行通信,分层由高到低分别为:应用层、传输层、网络层、数据链路层、物理层。发送端从应用层往下走,接收端从数据链路层网上走。
4.应用层:把以上部分结合自己的本机信息,封装成一个http请求数据包(http请求报文);
5.传输层:发起TCP的三次握手,建立TCP连接,TCP为了方便传输,分割大块的数据,并为它们编号,方便服务器接收时能准确地还原报文信息,这里传输层的TCP协议为传输报文提供可靠的字节流服务;
6.网络层:IP协议把TCP分割好的各种数据包封装到IP包里面传送给接收方,再通过ARP地址解析协议获取IP地址对应的MAC地址;
7.数据链路层:在找到对方的MAC地址后,已被封装好的IP包再被封装到数据链路层的数据帧结构中,将数据发送到数据链路层传输,再通过物理层的比特流传输出去
8.到这一步,客户端发送请求的阶段结束了.
9.接收端的服务器在链路层接收到数据包,再层层向上直到应用层。这过程中包括在传输层通过TCP协议将分段的数据包重新组成原来的HTTP请求报文。
10.服务器响应http请求;
11.浏览器得到html代码
12.浏览器解析html代码,根据响应报文里的状态码做判断,200表示请求成功,可以直接进入渲染流程;3开头的要重定向,4开头表示客户端的一些错误,5开头表示服务器的一些错误。
— —涉及到的协议有:
17. 简述HTTP请求的报文格式。
【链接】---- HTTP请求/响应报文结构
HTTP请求报文由状态行、请求头、空行和请求体4个部分组成,如下:
//例:
POST / HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 40
Connection: Keep-Alive
sex=man&name=Professional
18. HTTP的长连接是什么意思。
在HTTP/1.0中,默认使用的是短连接。也就是说,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。如果客户端浏览器访问的某个HTML或其他类型的 Web页中包含有其他的Web资源,如JavaScript文件、图像文件、CSS文件等;当浏览器每遇到这样一个Web资源,就会建立一个HTTP会话。
一般情况下,一旦服务器向浏览器发送了请求数据,它就要关闭TCP连接了,但是如果在http请求头部加入了:Connection:keep-alive,TCP连接会保持打开状态,保持连接可以节省为每个请求建立连接所需的时间,还节约了网络带宽。
但从 HTTP/1.1起,默认使用长连接,用以保持连接特性。在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。缺点是影响并发性能。
19. 如何理解HTTP协议的无状态性。
HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。
缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。
另一方面,在服务器不需要先前信息时它的应答就较快。
20. HTTP有哪些method
【链接】------- 常见的HTTP Method深度解析
http0.9只支持get方法
http1.0支持:GET、POST、HEAD
http1.1支持:GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE、CONNECT(后五种是http1.1新增的)
GET | 对服务器资源的简单请求 |
POST | 用来发送包含用户提交的表单数据的请求 |
HEAD | HEAD 方法与 GET 方法的行为很类似,但服务器在响应中只返回首部。不会返回实体的主体部分。这就允许客户端在未获取实际资源的情况下,对资源的首部进行检查。 |
PUT | 与 GET 从服务器读取文档相反,PUT 方法会向服务器写入文档。 注: POST 用于向服务器发送数据。PUT 用于向服务器上的资源(例如文件)中存储数据。 |
TRACE | TRACE 请求会在目的服务器端发起一个 环回 诊断。行程最后一站的服务器会弹回一条 TRACE 响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间 HTTP 应用程序组成的请求 / 响应链上,原始报文是否,以及如何被毁坏或修改过。 |
OPPTIONS | 请求 Web 服务器告知其支持的各种功能;通过使用 OPTIONS,客户端可以在与服务器进行交互之前,确定服务器的能力,这样它就可以更方便地与具备不同特性的代理和服务器进行互操作了。 |
DELETE | 请服务器删除请求 URL 所指定的资源。 但是,客户端应用程序无法保证删除操作一定会被执行。因为 HTTP 规范允许服务 器在不通知客户端的情况下撤销请求。 |
CONNECT | CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。 |
【GET和POST的区别以及数据包的格式】
【链接】----- GET和POST两种基本请求方法的区别
GET | POST | |
对参数的限制 | GET把参数包含在URL中,所以参数是有长度限制的,并且只允许参数为ASCII字符 | POST把参数包含在Request body中,参数没有长度和类型的限制 |
编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencoded or multipart/form-data。为二进制数据使用多重编码 |
缓存 | GET请求可以被浏览器cache | POST不会 |
安全性 | 参数直接暴露在URL上,所以 不能传递敏感信息,不安全 |
更安全,用在表单数据的提交 |
请求过程 | 浏览器会把http header和data一并发送出去,服务器响应200 | 浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok |
幂等性 | 满足幂等性,只是查询数据,不会影响到资源的变化 | 不满足幂等性,因为调用多次,都将产生新的资源 |
【注意】HTTP幂等方法,是指无论调用这个url多少次,并不影响资源。
21. 说说你知道的几种HTTP响应码,比如200, 302, 404。
状态码分类 | 分类描述 |
1** | 信息,服务器收到请求,需要请求者继续执行操作 |
2** | 成功,操作被成功接收并处理 |
3** | 重定向,需要进一步的操作以完成请求 |
4** | 客户端错误,请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
100 Continue——继续,客户端应继续其请求
200 OK——服务器成功返回网页
302 Move temporarily——临时重定向
403 Forbidden——服务器理解请求客户端的请求,但是拒绝执行此请求,即权限不足
404 Not Found——服务器无法根据客户端的请求找到资源(网页),即资源不存在
405 Method Not Allowed——请求方法不允许
500 Internal Server Error——服务器内部错误
502 Bad Gateway——服务器网关错误
503 Service Unavailable——服务器超时
【链接】------- http状态码
22. http1.0和http1.1有什么区别。
(1)长连接
(2)节约宽带
HTTP 1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端如果接受到100,才开始把请求body发送到服务器。这样当服务器返回401的时候,客户端就可以不用发送请求body了,节约了带宽。
(3)HOST域
http1.1中,客户端请求的头信息新增了Host头域,用来指定服务器的域名,这样就可以发往同一服务器上的不同网站(在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址)。
(4)分块传输编码
在http1.0中,使用Content-Length字段的前提条件是服务器发送回应之前,必须知道回应的数据长度;而对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成才能发送数据,显然这样的效率不高。
在http1.1中,可以不适用Content-Length字段,而使用分段传输编码(chunked transfer encoding)。只要请求或回应的头信息有Transfer-Encoding: chunked字段,就表明回应将由数量未定的数据块组成。
(5)缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来作为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
(6)错误通知的管理
在http1.1中新增了24个错误状态码
(7)HTTP Pipelining(HTTP管线化)
23. 什么是分块传送。
在http1.0中,使用Content-Length字段的前提条件是服务器发送回应之前,必须知道回应的数据长度;而对于一些很耗时的动态操作来说,这意味着,服务器要等到所有操作完成才能发送数据,显然这样的效率不高。
在http1.1中,可以不使用Content-Length字段,而使用分段传输编码(chunked transfer encoding)。只要请求或回应的头信息有Transfer-Encoding: chunked字段,就表明回应将由数量未定的数据块组成。
每个非空的数据块之前,会有一个16进制的数值,表示这个块的长度。最后是一个大小为0的块,就表示本次回应的数据发送完了。下面是一个例子。
НТТP/1.1 200 Ок
Content-Type: text/plain Transfer-Encoding: chunked
25
This is the data in the first chunk
1C
and this is the second one
3
con
8
sequence
0
24. HTTPS的加密方式是什么,讲讲整个加密解密流程。
【介绍】
【对称加密和非对称加密的区别】
【Https加密】—— 对称加密和非对称加密结合方式
【总结】这样浏览器和服务器就共享一个对称加密密钥B,重要的是不会被拦截到。只在传输密钥B的时候进行了一次非对称加密,之后就用对称加密进行传送数据。
25. Http和Https的三次握手有什么区别。
【Https的握手过程】
1,客户端输入https网址,链接到server443端口;
2,服务器手中有一把钥匙和一个锁头,把锁头传递给客户端。数字证书既是公钥,又是锁头
3,客户端拿到锁头后,生成一个随机数,用锁头把随机数锁起来(加密),再传递给服务器。这个随机数成为私钥,现在只有客户端知道
4,服务器用钥匙打开锁头,得到随机数。该随机数变成了新的锁头,把内容锁起来(加密),再传递给客户端。这个随机数服务器也知道了,并且用它来加密数据
5,客户端用自己手中的钥匙(随机数),解密后得到内容。客户端用私钥解密数据
6,接下来的客户端和服务器的数据交换都通过这个随机数来加密。只有客户端和服务器知道私钥,所以数据传输是安全的,不是明文的
【两者区别】
26. 如何避免浏览器缓存。
在引用js、css文件的url后边加上 ?+Math.random()
27. Session和cookie的区别。
Cookie | Session |
保存在客户端 | 保存在服务器 |
只能保存字符串,且编码只能是ISO8859-1 | 可以保存任意类型 |
Cookie被禁用后失效 | Cookie被禁用后可以通过url重写来获取Session |
安全性较低 | 安全性较高 |
Cookie保存的数据大小有限制,一般在4k左右 | 只要服务器内存够,理论上无限制 |
分担了服务器压力 | 增加了服务器压力 |
28. 简单解释一些ARP协议的工作过程
(1)首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。
(2)当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机IP地址,源主机MAC地址,目的主机的IP地址。
(3)当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。
(4)源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。
29. ICMP协议简介
【简介】
【典型应用】
30. DNS域名解析协议的A记录、CNAME
【A记录】A (Address) 记录是用来指定主机名(或域名)对应的IP地址记录。用户可以将该域名下的网站服务器指向到自己的web server上。同时也可以设置您域名的二级域名。
【CNAME记录】别名记录。这种记录允许您将多个名字映射到另外一个域名。通常用于同时提供WWW和MAIL服务的计算机。例如,有一台计算机名为“host.mydomain.com”(A记录)。它同时提供WWW和MAIL服务,为了便于用户访问服务。可以为该计算机设置两个别名(CNAME):WWW和MAIL。这两个别名的全称就 http://www.mydomain.com/和“mail.mydomain.com”。实际上他们都指向 “host.mydomain.com”。
31. 加密算法有哪些?
对称加密算法——加密和解密都是同一个密匙:AES、DES;
非对称加密算法——公钥和私钥成对出现:RSA、DSA;
不需要秘钥的散列算法:MD5、SHA-1。
MD5加密算法 —— 消息摘要算法,Hash算法一类
MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要
AES加密算法 —— 对称加密算法
对称加密的秘钥传输问题:一般都是客户端向服务端请求对称加密的密钥,而且密钥还得用非对称加密加密后再传输;
RSA加密算法 —— 非对称加密算法
使用RSA一般需要产生公钥和私钥,当采用公钥加密时,使用私钥解密;采用私钥加密时,使用公钥解密。Java程序如下:
public class RSAEncrypt {
private static Map keyMap = new HashMap(); //用于封装随机产生的公钥与私钥
public static void main(String[] args) throws Exception {
//生成公钥和私钥
genKeyPair();
//加密字符串
String message = "df723820";
System.out.println("随机生成的公钥为:" + keyMap.get(0));
System.out.println("随机生成的私钥为:" + keyMap.get(1));
String messageEn = encrypt(message,keyMap.get(0));
System.out.println(message + "\t加密后的字符串为:" + messageEn);
String messageDe = decrypt(messageEn,keyMap.get(1));
System.out.println("还原后的字符串为:" + messageDe);
}
/**
* 随机生成密钥对
* @throws NoSuchAlgorithmException
*/
public static void genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024,new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new
String(Base64.encodeBase64((privateKey.getEncoded())));
// 将公钥和私钥保存到Map
keyMap.put(0,publicKeyString); //0表示公钥
keyMap.put(1,privateKeyString); //1表示私钥
}
1.B-树、B+树
利用索引查询的时候不能将所有索引一次性加载到内存,一般是逐一加载磁盘页,这里的磁盘页对应着索引树的节点;
二叉查找树查询的时间复杂度是O(logN),最坏情况下,磁盘IO的次数等于树的高度;
因此为了减少磁盘IO次数,需要将原本瘦高的树变得矮胖。
【B-树】—— 多路平衡查找树,它的每一个节点最多包含m个孩子,m被称为B树的阶,而m的大小取决于磁盘页的大小
可以将原本“瘦高”的树结构变得“矮胖”,一个m阶的B树具有如下的特征:
【B+树】由于B+树的中间节点没有存放数据,同样大小的磁盘页能放更多的节点元素,更加“矮胖”,查询时磁盘IO的次数变少
在数据库的聚集索引中,叶子节点直接包含数据。在非聚集索引中,叶子节点带有指向数据的指针。
【B-数与B+树的比较】
B-树查找只要找到匹配元素的节点即可,匹配元素可能处于中间节点,也可能处于叶子节点,所以的查找性能并不稳定
B+树比B-树的优势:每次都得查询到叶子节点,所以B+树查询性能稳定;磁盘IO次数更少;范围查询更简便。
2. 红黑树
【链接】---- 红黑树的实现
【红黑树的应用场景】TreeSet、JDK1.8的HashMap底层数据结构用的红黑树
【红黑树的规则】
【红黑树各种操作的时间复杂度】能保证在最坏情况下,均为O(logn)
【为什么使用红黑树】
相较于AVL树,红黑树对平衡性的要求没有AVL树那么高,牺牲了一定的平衡性,但是也是近似平衡的;同时AVL树为了维持高度平衡,每次插入、删除操作比较复杂、耗时,使用AVL的代价比较高;所以红黑树是在查询性能和维护平衡的开销上做了一个平衡,使得插入、删除、查找各种操作性能都比较稳定,均为O(logn)。
相较于哈希表,权衡三个因素: 查找速度, 数据量, 内存使用,可扩展性,有序性。
hash查找速度会比红黑树快,而且查找速度基本和数据量大小无关,属于常数级别;而红黑树的查找速度是log(n)级别。并不一定常数就比log(n) 小,因为hash还有hash函数的耗时。当元素达到一定数量级时,考虑hash。但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么hash可能会让你陷入尴尬,特别是当你的hash对象特别多时,你就更无法控制了,而且 hash的构造速度较慢。
【左旋】左旋中的左意味着被旋转的结点将变成一个左结点
右旋的操作和左旋的代码是对称的,左旋和右旋的时间复杂度均为O(1)
private void rotateLeft(Node x){
Node y = x.right; //设置y结点,为x结点的右结点
//旋转开始
x.right = y.left; //x的右结点设置为y的左结点
if(y.left != null){ //如果y的左结点不为空,则将y的左结点的父结点指向x
y.left.parent = x;
}
y.parent = x.parent; //把y的父结点改为x的父结点
if(x.parent == null){
this.root = y; //如果x的父结点为空。那么y就是root结点
}
else if(x == x.parent.left){
x.parent.left = y;
}
else x.parent.right = y;
y.left = x;
x.parent = y;
}
【右旋】右旋中的右意味着被旋转的结点将变成一个右结点
private void rotateRight(Node y){
Node x = y.left; //设置x结点,为y结点的左结点
//旋转开始
y.left = x.right; //y的左结点设置为x的右结点
if (x.right != null)
x.right.parent = y; //把x右结点的父结点指向y结点
x.parent = y.parent; //把x的父结点改为y的父结点
if (y.parent == null)
this.root = x; //如果y的父结点为空。那么x就是root结点
else if (y == y.parent.left) //y为它父结点的左结点
y.parent.left = x; //把y的父结点的左结点改为x结点
else y.parent.right = x; //把y的父结点的右结点改为x结点
x.right = y; //x结点的右结点为y
y.parent = x; //y的父结点为x
}
【插入】
3. List
4. 栈(Stack)—— 先进后出
5. 队列(Queue)—— 先进先出
【非阻塞队列】
实现Queue的类 | 特点 | 数据结构 | 线程安全 |
Deque,双端队列 | 既可以当成双端队列使用,也可以被当成栈来使用 | ||
PriorityQueue,优先级队列 | 保持队列的顺序是按照元素的大小重新排序的 | 数据结构是堆,底层是用数组保存数据 | 不安全 |
ConcurrentLinkedQueue |
增加、删除O(1),查找O(n) | 基于链表 | 安全 |
【阻塞队列】线程阻塞时,不是直接添加或者删除元素,而是等到有空间或者元素时,才进行操作。
实现Queue的类 | 特点 | 数据结构 | 使用场景 |
DelayQueue | 其中的元素只有当其指定的延迟时间到了才能取到,插入数据不会阻塞,只有取数据才会阻塞 | 没有大小限制的队列 | 管理一个超时未响应的连接队列 |
SynchronousQueue | 内部没有容器的队列 | 可以缓存线程的线程池 newCachedThreadPool 的阻塞队列 |
|
PriorityBlockingQueue | 基于优先次序的队列 |
|
|
LinkedBlockingQueue | 基于链表的无界队列 | 只有一个线程的线程池 newSingleThreadExecutor 的阻塞队列 |
|
ArrayBlockingQueue | 基于数组的有界队列 |
6. 哈希散列表
【hash冲突解决的方法】
7. 图
【图的表示方法】邻接矩阵、邻接表、逆邻接表、十字链表
【图的深度优先遍历DFS】回溯法
【图的广度优先遍历BFS】
8. 位图
1. 递归和迭代的区别
递归:调用自己的编程方法,即自己调用自己,把一个大型的复杂的问题转化为一个与原问题相似的规模较小的问题来解决,使用递归的时候需要注意的两点:
迭代:利用变量的原值推算出变量的一个新值,如果递归是自己调用自己的话,,迭代就是A不停的调用B。
递归中一定有迭代,但是迭代中不一定有递归,大部分可以相互转换。能用迭代的不用递归,递归调用函数,浪费空间,并且递归太深容易造成堆栈的溢出。
//这是递归
int funcA(int n)
{
if(n > 1)
return n+funcA(n-1);
else
return 1;
}
//这是迭代
int funcB(int n)
{
int i,s=0;
for(i=1;i
2. 八大排序
快排空间复杂度最优O(logn),最差O(n)
快排优化:基准元素的选取,采用random随机获得一个下标,将该下标对应的元素与递归数组第一个元素交换
【稳定性】
【复杂度】
【Arrays.sort实现原理】
【链接】---- Arrays.sort底层原理
如果元素个数少于47这个阀值,就用插入排序;
如果元素个数少于286这个阈值,就进入快排;
如果超过286,进入伪归并。
3. 二叉树有关算法
【链接】---- 20道题搞定BAT二叉树算法题
(1)求二叉树中的节点个数
【递归解法】
(2)求二叉树的最大层数(最大深度)
【递归解法】
(3)求二叉树的最小深度——从根节点到最近叶子节点的最短路径上的节点数量
return (left == 0 || right == 0) ? left + right + 1 : Math.min(left, right) + 1;
(4)前序、中序、后序遍历
【掌握递归和非递归解法】
(5)分层遍历、自下而上分层遍历
(6)按之字形顺序打印二叉树
(7)求二叉树第K层的节点个数
(8)判断两棵二叉树是否结构相同
(9)判断二叉树是不是平衡二叉树
(10)求二叉树的镜像
(11)求二叉树中两个节点的最低公共祖先节点
(12)求二叉树的直径
(13)由前序遍历序列和中序遍历序列重建二叉树
(14) 从中序与后序遍历序列构造二叉树
(15)判断二叉树是不是完全二叉树
(16) 树的子结构
(17) 二叉树中和为某一值的路径
(18)二叉树的下一个结点
(19)序列化二叉树
(20)二叉搜索树的第k个结点
4. 拓扑排序——有向无环图
拓扑排序主要用来解决有向图中的依赖解析问题;
【链接】---- LeetCode 207.课程表 Java实现
5. 动态规划
数塔取数问题、编辑距离、矩阵取数问题、01背包问题
【链接】---- 动态规划算法学习笔记 Java实现
6.贪心算法
【存在的问题】
【应用领域】
旅行商问题、马踏棋盘、背包、装箱等
7.回朔算法
8.Dijkstra算法
9.深度优先和广度优先
一亿个数求第K大的数
基本:
先构造K个数的最小堆;
然后遍历一亿个数,每次和堆中最小数进行比较,如果更大则往堆中插入该数;
升级:
采用分治的办法,使用将一亿个数分成N份,然后采用多线程或者多台服务器,分别得到N份最大K个最大的数;
然后对N*K个数求最大的K个数。
【链接】---- 海量数据——top K问题
【链接】---- java海量大文件数据处理方式
1. 给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?
2. 有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。
1. 进程和线程的区别
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位;
线程:是进程的一个执行单元。
【区别】
进程是资源分配的最小单位,线程是程序执行的最小单位。
进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换线程远比切换进程的开销要小很多,同时创建一个线程的开销也比进程要小很多。
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。
但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
3. 进程调度算法
4. 进程间通信的几种方式
管道(pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
有名管道(named pipe):有名管道也是半双工的通信方式,但是它允许无亲缘关系进程之间的通信。
消息队列(message queue):消息队列是消息的链表,存放在内核中并由消息队列表示符表示。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限制等缺点。
共享内存(shared memory):共享内存就是映射一段内被其它进程所访问的内存,共享内存由一个进程创建,但是多个进程都可以访问。共享内存是最快的IPC,它是针对其它进程通信方式运行效率低的而专门设计的。它往往与其它通信机制。如信号量,配合使用,来实现进程间的同步和通信。
套接字(socket):套接字也是进程间的通信机制,与其它通信机制不同的是,它可以用于不同机器间的进程通信。
信号(signal):信号是一种比较复杂的通信方式,用于通知接受进程进程某个时间已经发生。
信号量(semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁的机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此它主要作为不同进程或者同一进程之间不同线程之间同步的手段。
5. OS死锁相关的问题
什么是死锁?哲学家就餐问题
死锁的必要条件
死锁的应对方法
死锁的检测与恢复
死锁的动态避免:银行家算法
死锁的静态防止:破坏死锁的必要条件之一
6. 段页式内存管理
7. 页面置换算法
8. 磁盘调度算法
9. Linux系统常用的命令有哪些?
10.select、poll、epoll的区别
【链接】---- select、poll、epoll的区别
1【分别介绍】
select:仅知道有I/O事件发生了,但是不知道是哪个流,所以只能无差别轮询所有流;单个进程可监视的fd数量被限制,即能监听的端口号有限;需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
poll:本质上和select没有区别,但是它没有最大连接数的限制,原因是它基于链表来存储的,但是同样会大量的fd的数组被整体复制于用户态和内核地址空间之间。
epoll:没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
【使用场景】
1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。
2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。
【链接】---- 互联网公司面试必问的Mysql题目
--开启事务:(两种)
start transaction;
begin;
--设置保存点
SAVEPOINT pointName;
--回滚到保存点
ROLLBACK TO SAVEPOINT pointName;
1. 数据库事务的ACID是什么。
答:指事务的四大特性
原子性(Atomicity): 包含的所有操作要么全部成功,要么全部失败回滚。------------为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log),然后进行数据的修改,如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。
一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性(Isolation): 指的是在并发环境中,当不同的事务同时操作相同的数据时,每个事务都有自己完整的数据空间,多个并发事务之间相互隔离。
持久性 (Durability):一旦事务提交成功,事务中的所有数据操作都必须持久化保存到数据库中,即使提交事务后,数据库崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。---------和Undo Log相反,Redo Log记录的是新数据的备份,为了保证持久性,必须在事务提交前将Redo Log持久化。
2. 什么是脏读、不可重复读、幻读。
脏读(Dirty Read):一个事务读取到了另一个事务没有提交的数据。
幻读(Phantom Read):一个事务读取2次,得到的记录条数不一致,由于2次读取间另外一个事务对数据进行了增删
不可重复读(NonRepeatable Read):一个事务读取同一条记录2次,得到的结果不一致,由于在2次读取之间另外一个事务对此行数据进行了修改。
3. 数据库隔离级别有哪些,各自的含义是什么,MYSQL默认的隔离级别是是什么。
【链接】---- 浅析Mysql的隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
未提交读——Read uncommitted | 可能 | 可能 | 可能 |
已提交读——Read committed | 不可能 | 可能 | 可能 |
可重复读——Repeatable read(默认) | 不可能 | 不可能 | 可能 |
可串行化——Serializable | 不可能 | 不可能 | 不可能 |
4. MYSQL有哪些存储引擎,各自优缺点。
(MySQL5.5以后默认使用InnoDB 引擎)
MYSQL存储引擎 | 优点 | 缺点 |
InnoDB | 支持事务 行级锁 支持外键关联 支持热备份,灾难恢复性好 |
InnoDB 中不保存表的具体行数, 执行select count(*) from table时, InnoDB要扫描一遍整个表来计算有多少行 |
MyISAM | 执行速度比InnoDB类型更快 如果执行大量的SELECT,MyISAM是更好的选择 |
不支持事务 使用表级锁,并发性差 主机宕机后,MyISAM表易损坏,灾难恢复性不佳 |
MEMORY | 提供内存表,也不支持事务和外键。 显著提高访问数据的速度,可用于缓存会频繁访问的、 可以重构的数据、计算结果、统计值、中间结果。 |
使用表级锁,虽然内存访问快,但如果频繁的读写, 表级锁会成为瓶颈 只支持固定大小的行。Varchar类型的字段会存储为 固定长度的Char类型,浪费空间 不支持TEXT、BLOB字段。 服务器重启后数据会丢失,复制维护时需要小心 |
【链接】------- MySQL有哪些存储引擎,各自的优缺点,应用场景
【链接】------- MySQL架构设计——MyISAM存储引擎与InnoDB存储引擎
5. Mysql框架结构
在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page);
【表空间】
【段】
【区/簇】
【页】
6.InnoDB存储引擎的锁的算法有三种:
Record lock:单个行记录上的锁
Gap lock:间隙锁,锁定一个范围,不包括记录本身
Next-key lock:record+gap 锁定一个范围,包含记录本身
7.(1)乐观锁和悲观锁是什么,INNODB的标准行级锁有哪2种,解释其含义。
【链接】------- MySQL 乐观锁与悲观锁
乐观锁:增加版本标识字段或时间戳字段(MVCC)
悲观锁:SELECT ... FOR UPDATE;
INNODB的标准行级锁:共享锁(Share Lock)和排它锁(eXclusive Lock)
共享锁(Share Lock):共享锁又称读锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
用法:SELECT ... LOCK IN SHARE MODE;
排它锁(eXclusive Lock):排他锁又称写锁,如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
用法:SELECT ... FOR UPDATE;
对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁.。
7.(2)Innodb中MVCC的含义,如何实现的。
【链接】---- Mysql中MVCC的使用及原理详解
8. 举一个数据库死锁的例子,mysql怎么解决死锁。
----例:事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。事务A和事务B在相互等待对方的资源释放,就是进入了死锁。当出现死锁以后,有两种策略:
----减少死锁的主要方向,就是控制访问相同资源的并发事务量;
----如何尽可能避免:
//9. MySQL的锁
【根据加锁的范围分】全局锁、表级锁、行锁
【全局锁】
【表级锁】一共有两种:表锁,元数据锁(MDL)
----表锁-----:语法是lock tables ... read/write
----元数据锁-----:不需要显示使用,在访问一个表的时候会被自动加上,当对一个表做增删改查操作的时候,会加上MDL读锁,当要对表结构变更的时候,会加上MDL写锁;读锁之间不互斥,可以多线程同时对一个表做增删改查;读写锁之间、写锁之间是互斥的。事务中 的MDL锁,在语句执行开始时申请,但是在语句执行完以后不会马上释放,而会等到事务提交后再释放
----如何安全地给小表加字段-----:因为加字段就是对表结构做更改,会自动加上MDL写锁。 首先要解决长事务问题,事务不提交,就会一直占着MDL锁,就拿不到MDL写锁以进行加字段操作。另外,如果是热点表,增删改查的请求很频繁,只能在alter table 语句里设定等待MDL写锁的时间,如果能够在这个时间里拿到写锁最好,如果拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员再重试命令重复这个过程。
【行锁】在innodb事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放的,而是要等到事务结束时才释放,这个就是两阶段锁协议。
10. Mysql的索引原理,索引的类型有哪些,如何创建合理的索引,索引如何优化。
【链接】------- MySQL性能调优——索引详解与索引的优化
【链接】------- 索引基础——B-Tree、B+Tree、红黑树、B*Tree数据结构
【索引的优缺点】
— —优点:
— —缺点:
— —判断是否应该建索引的条件:
1、较频繁的作为查询条件的字段应该创建索引
2、唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
3、增、删、改操作较多的数据库字段不适合建索引
4、数据量大的表才适合建立索引
— —索引使用的注意事项:
— —聚集索引和非聚集索引的区别:
根本区别是表记录的物理顺序与索引的排列顺序是否一致
聚集索引 | 非聚集索引 |
表记录的物理顺序与索引的排列顺序一致,查询效率快 | 非聚集索引叶节点仍然是索引节点,即数据记录的地址,二次查询问题 |
修改慢,修改的时候会根据索引列的排序对数据页重排 | 非聚集索引层次多,不会造成数据重排 |
【索引的类型】普通索引,唯一索引,主键索引,全文索引,复合索引
— —普通索引(index)
//标准语句:
ALTER TABLE table_name ADD INDEX index_name (column_list)
CREATE INDEX index_name ON table_name (column_list);
//还有建表的时候创建亦可
CREATE TABLE table_name (
ID INT NOT NULL,
column_list VARCHAR(16) NOT NULL,
INDEX [index_name ]
(column_list(length))
);
— —唯一索引(unique)
ALTER TABLE table_name ADD UNIQUE (column_list)
CREATE UNIQUE index_name ON table_name (column_list)
//还有建表时创建
CREATE TABLE table_name (
ID INT NOT NULL,
column_list VARCHAR(16) NOT NULL,
UNIQUE [index_name ]
(column_list(length))
);
— —主键索引(把某列设为主键,则就是主键索引)
它是一种特殊的索引,不允许有空值,一般在建表的时候同时创建主键索引
按数据结构分:BTree索引,Hash索引,Full-Text索引
索引类型 | 采用的引擎 |
BTree索引 | MyISAM、Innodb |
Hash索引 | Memory |
Full-Text索引 | 只有MyISAM支持 |
MyISAM和Innodb的存储结构均使用B+树的数据结构,具体实现区别如下表:
MyISAM引擎 | Innodb引擎 |
索引结构的叶子节点数据域,存放的不是实际的数据记录,而是数据记录的地址 | 索引结构的叶子节点数据域,存放的就是实际的数据记录 |
索引文件与数据文件分离 | 索引文件与数据文件不分离 |
非聚簇索引 | 聚簇索引 |
先按照B+Tree的检索算法检索,找到指定关键字,则取出对应数据域的值,作为地址取出数据记录 | 对于主索引,叶子节点的数据域会存放表中所有的数据记录;对于辅助索引此处会引用主键,检索的时候通过主键到主键索引中找到对应数据行 |
11. 某个表有近千万数据,CRUD比较慢,如何优化,怎么优化全表扫描?
【链接】---- MySQL千万级数据处理
— —怎么优化全表扫描?
12.怎么看执行计划,如何理解其中各个字段的含义
【链接】------ MySQL优化系列
【链接】------ 优化 sql 语句的一般步骤
-----执行计划:
-----各字段的含义:
13. 高并发下,如何做到安全的修改同一行数据。
14. 如何避免长事务对业务的影响?
答:这个问题应该从应用开发端和数据库端分别来看。
【应用开发端】
【数据库端】
15.数据库自增主键可能的问题。
16.MYSQL的主从延迟怎么解决。
17. 你做过的项目里遇到分库分表了吗,怎么做的,有用到中间件么,比如sharding jdbc等,他们的原理知道么。
主要两种拆分,垂直拆分和水平拆分
【垂直拆分】减少单表字段数量,优化表结构
【水平拆分】
【分库分表中间件】
—— IDBC驱动版的优点:
—— Proxy版的优点:
【Sharding-JDBC】
【链接】---- 分库分表
18. mysql如何批量删除大量数据,比如删除一个表里面的前10000行数据?
假设有一个表(syslogs)有1000万条记录,需要在业务不停止的情况下删除其中statusid=1的所有记录,差不多有600万条, 直接执行 DELETE FROM syslogs WHERE statusid=1 会发现删除失败,因为lock wait timeout exceed的错误。
【最小化的减少锁表时间的方案】
方法一:使用limit参数分批删除,DELETE FROM syslogs WHERE statusid=1 ORDER BY statusid LIMIT 10000;
方法二:
1、选择不需要删除的数据,并把它们存在一张相同结构的空表里
2、重命名原始表,并给新表命名为原始表的原始表名
3、删掉原始表
方法三:(删除带索引的表)
在删除数据之前删除索引,然后删除其中无用的数据,删除完成后重新创建索引
— —比如删除一个表里面的前10000行数据?
19.参数调优
20. Mysql中的左/右/内连接
【语法】
select
*
from
表A a -- 主表
left/right/inner join 表B b on 连接条件 -- 子表
【作用】
将A表中的每一条数据 和 B 表中的每一条数据 进行连接,保留
满足连接条件的数据
对于内连接(inner join )而言: 只保留所有满足条件的字段
对于左连接(left join) 而言: 加上 A 表中 所有在 B 表找不到满足连接条件的数据,B 表部分的字段值为null
对于右连接(right join) 而言: 加上 B 表中 所有在 A表找不到满足连接条件的数据,A表部分的字段值为null
21. 数据库三范式
【链接】---- 通俗理解数据库范式
【引言】
1)第一范式:属性不可再分,原子性
举例:电话这个属性,还可以分为手机和座机,所以不符合第一范式;
2)第二范式:在1NF的前提下,要求所有非主属性完全依赖于候选码,不能产生部分依赖
举例:上面这个表的候选码(姓名,课程),但是教材只依赖于课程,不是完全依赖;
3)第三范式:在2NF的前提下,要求所有非主属性直接依赖于候选码,不能产生传递依赖
举例:老师职称依赖于老师,老师依赖于候选码,这是传递依赖
4)BC范式:在3NF的前提下,主属性不依赖于主属性
5)第四范式(4NF):要求把同一表内的多对多关系删除
6)第五范式(5NF):从最终结构重新建立原始结构
22.ER图(Entity-Relationship Approach)
23. mysql中in 和exists 区别。
MySQL中In与Exists的区别(1)
24. having和where的区别
1.having只能用在group by之后,对分组后的结果进行筛选(使用having的前提条件是分组)。
2.where肯定在group by 之前。
3.where后的条件表达式里不允许使用聚合函数,而having可以。
25. 当一个查询语句同时出现了where,group by,having,order by的时候,执行顺序和编写顺序
1.执行where xx对全表数据做筛选,返回第1个结果集。
2.针对第1个结果集使用group by分组,返回第2个结果集。
3.针对第2个结果集中的每1组数据执行select xx,有几组就执行几次,返回第3个结果集。
4.针对第3个结集执行having xx进行筛选,返回第4个结果集。
5.针对第4个结果集排序。
为什么需要视图?
对于同一张表,A用户只关新部分字段,B用户只关心另一些字段,那么此时把全部的字段都显示给他们看,是不合理的。
所以我们需要提供了一个表的“视图”,给到他们想看的数据的同时,屏蔽掉其它字段的数据;
使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;
但是性能较差。
视图的是怎么实现的?
(1)视图是一种虚表,向视图提供数据内容的语句为 SELECT 语句,可以将视图理解为存储起来的 SELECT 语句
(2)视图没有存储真正的数据,真正的数据还是存储在基表中,一个基表可以有0个或多个视图
(3)视图中,用户可以使用SELECT语句查询数据,也可以使用INSERT,UPDATE,DELETE修改记录
【存储过程】
一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数,就像java的方法一样,可以重复的被调用。
create procedure proc1 --创建一个存储过程
@para1 varchar(50),@para2 varchar(50)
as
begin
--在存储过程中处理SQL
select * from bank
end
优点:
缺点:
【触发器】
由事件来触发某种操作,触发器经常用于加强数据的完整性约束和业务规则等。 触发器创建语法四要素:
触发器基本语法:
create trigger triggerName
after/before insert/update/delete on 表名
for each row --这句话在mysql是固定的
begin
sql语句;
end;
摒弃触发器!触发器的功能基本都可以用存储过程来实现。
MySQL支持以下约束:主键约束、外键约束、唯一约束、非空约束
【主键约束】一个表只能有一个主键约束(索引)
--添加主键约束
alter table 表名 add primary key (列名); --可以有多个列名
【外键约束】
alter table 表名称 add foreign key (列名称) references 关联表名称(列名称);
【唯一约束】只能唯一不能重复
alter table 列名 add unique(列名称); --可以有多个列名称,用逗号隔开。
【非空约束】
alter table 表名 modify 列名 列类型 not null;
1. Redis是什么。简述它的优缺点。
2. Redis相比memcached有哪些优势?
3. Redis有哪几种数据淘汰策略?
达到最大内存限制(maxmemory)时,Redis 根据 maxmemory-policy
配置的策略, 来决定具体的行为。
4. Redis使用的优化和注意事项
【使用Key值前缀来作命名空间】虽然说Redis支持多个数据库(默认32个,可以配置更多),但是除了默认的0号库以外,其它的都需要通过一个额外请求才能使用。在使用前缀作为命名空间区隔不同key的时候,最好在程序中使用全局配置来实现。
【注意垃圾回收】在Redis中保存数据时,一定要预先考虑好数据的生命周期(使用Expire设置过期时间)。但是自动过期有一个问题,很有可能导致你还有大量内存可用时,就让key过期去释放内存,或者是内存已经不足了key还没有过期。你可以用一个ZSET来维护你的数据更新程度,你可以用时间戳作为score值,每次更新操作时更新一下score,这样你就得到了一个按更新时间排序序列串,快速找到最老的数据。
【Redis的Sharding机制】 学习
【单线程】
【缓存穿透】
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决:
【缓存雪崩】
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决:
【缓存击穿】
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。
解决:
5. 事务
不支持回滚!
Redis事务相关的命令:MULTI、EXEC、DISCARD和WATCH;
【用法】
【Redis事务错误处理】
Redis事务保证以下两点:
为什么Redis不支持回滚
6. AOF和RDB
【链接】---- 深入redis持久化
【链接】---- Redis持久化的几种方式
rdb
默认开启,aof
默认关闭。
当两种持久化机制都开启时,redis
重启恢复数据时加载aof
持久化的appendonly.aof
文件,而rdb
持久化的dump.rdb
文件不会被加载到内存中。
AOF(append only file)持久化
原理是将Reids的操作日志以追加的方式写入文件;
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录。
RDB持久化
原理是将Reids在内存中的数据库记录定时dump到磁盘上的RDB持久化;
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。
【两者比较】
二者选择的标准,就是看系统是愿意牺牲一些性能,换取更高的缓存一致性(aof),还是愿意写操作频繁的时候,不启用备份来换取更高的性能,待手动运行save的时候,再做备份(RDB)。RDB这个就更有些 最终一致性的意思了。
7.使用Redis实现分布式锁,缺点
8.Redis数据类型
【链接】---- redis应用场景
8. Redis的使用场景
(1)缓存——两种方式保存数据
方案一:读取前,先去读取Redis,如果没有数据,读取数据库,并将数据拉入Redis;
方案二:插入数据时,同时写入Redis。
(2)计数器
(3)新评论列表
【具体实现】
LPUSH latest.comments
LTRIM latest.comments 0 5000
LRANGE latest.comments 0 N
(4)排行榜
ZADD key
ZREVRANGE key 0 99
ZRANK key
(5)分布式锁与单线程机制
(6)队列
【基础知识】
【常用命令】
git reset HEAD
【git reset ,git checkout,git revert的区别】
在版本回退方面:
git checkout --
git reset 将HEAD指向的位置改变为之前的某个目标版本,但是reset后,目标版本之后的版本不见了
git revert 通过创建一个新的版本,这个版本的内容与我们要回退到的目标版本一样,但是HEAD指针指向这个新生成的版本;如果我们想恢复之前的某一版本的同时,又想保留该版本后面的版本,记录下这整个版本变动的流程,就可以用这个方法。
在分支方面:
git checkout
【fetch,merge,pull的区别】
【tag——定义一个有意义的名字(比如v1.2)与某个commit绑在一起】
1. 基本操作指令
---------------------------------------------------------------------------------
----------------------------------------------------------------------------------
------------------------------------------------------------------------------------
chmod设置文件权限,后面跟三个数字,分表表示不同用户或用户组的权限,比如【chmod 777】
------------------------------------------------------------------------------------
2. Linux网络配置常用命令
【链接】---- Linux网络配置常用命令
1. 获得int型最大值
(1 << 31) - 1;
2. 获得int型最小值
1 << 31;
3. 乘以2、除以2运算
n << 1; //乘以2
n >> 1; //除以2
4. 判断一个数的奇偶性
if((n&1) == 1) //真:奇数
5. 不用临时变量交换两个数
a ^= b;
b ^= a;
a ^= b;
6. 判断一个数是不是2的幂
boolean isFactorialofTwo(int n){
return n > 0 ? (n & (n - 1)) == 0 : false;
/*如果是2的幂,n一定是100... n-1就是1111....
所以做与运算结果为0*/
}
【资源】--- 阿里面试题总结
面向对象设计原则:
面向对象特性:封装、继承、多态
怎么理解Java的多态?
1. JAVA中的几种基本数据类型是什么,各自占用多少字节
数据类型 | 占用字节 | 数据类型 | 占用字节 |
byte | 1 | int | 4 |
boolean | 1 | long | 8 |
char | 2 | float | 4 |
short | 2 | double | 8 |
2. String,Stringbuffer,StringBuilder的区别?String类能被继承吗?为什么?
String:字符串常量,在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作。
StringBuffer: 字符串变量,线程安全(多个人同时访问一个字符串时,不会出现不可预知的错误),效率低
StringBuilder:字符串变量,线程不安全(多个人同时访问一个字符串时,会出现不可预知的错误),效率高
【String类不能被继承】因为String类被final修饰符修饰,是最终类,是不能被继承的,实现细节不允许改变。
3. Object常用方法
clone方法:克隆一个对象
getClass方法:返回对象的运行时类的class对象
finalize方法:不需要程序员手动调用,当对象被释放时,由gc调用finalize
equals方法:用于比较两个对象是否相等
hashCode方法:返回对象的hashCode值(如果没有重写,返回的是对象的内存地址)
toString方法:将对象转换为字符串表示形式
4. hashCode和equals方法。
(1)hashCode方法
【hashCode的作用】
【hashCode与equals的关系】
Error和Exception的区别,并列出5个运行时异常。
Error和Exception都继承自Throwable;
Exception | Error |
可以是可被控制的或不可控制的 | 总是不可控制的 |
表示一个由程序员导致的错误 | 用来表示系统错误或底层资源的错误 |
应该在应用程序级被处理 | 如果可能的话,应该在系统级被捕捉 |
运行时异常:
5. 讲讲类的加载,加载时机,类加载器,类的实例化顺序,双亲委派机制,如何打破。new一个对象的过程中发生了什么?
【类加载】当程序要使用某个类时,如果该类还没有被加载到内存中,则系统会通过加载、连接、初始化三步来对初始化类
【类加载的时机】(只加载一次,以下时机表示第一次的时候)
【类加载器】负责将.class文件加载到内存中,并为之生成对应的Class对象
【类的实例化顺序】
1. 当对象所在的类被加载时,
注:静态属性和静态代码块只会执行一次,换言之,静态属性和静态代码块在类加载时执行,而类只会加载一次
2. 对象所在的类被加载以后,当对象被创建时,
【双亲委派机制】
某个特定的类加载器接到类加载的请求时,首先将加载任务委托给父类加载器,依次递归,只有当父类加载器无法完成加载请求时,子类才会尝试自己去加载。
【如何打破双亲委派机制】
【new一个对象的过程】
6. 反射的原理,反射创建类实例的三种方式是什么。Class.forName和ClassLoader区别.
【反射原理】
Java程序可以加载一个在运行时才得知类名称的class,获悉其完整构造,并创建其对象,或对其任意属性设置,或调用其任意方法,这种动态获取的信息及动态调用对象的方法的功能称为Java语言的反射机制。
【class对象的三种创建方式】
【Class.forName和ClassLoader的区别】
7. 动态代理的几种实现方式,分别说出相应的优缺点。为什么CGlib方式可以对接口实现代理。
【链接】---- 动态代理
【动态代理】
动态代理是在运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。
【JDK动态代理】
【cglib动态代理】
【动态代理的应用】
AOP面向切面编程,实现方式就是通过对目标对象的代理在切点 前后加入通知,完成统一的切面操作;
实现AOP的技术,主要分为两大类:
一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;
二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码;
默认的策略是如果目标类是接口,则使用JDK动态代理技术,如果目标对象没有实现接口,则默认会采用CGLIB代理。
为什么CGlib方式可以对接口实现代理。
CGLib代理的原理是通过二进制流生成一个动态class,该class继承被代理类以实现动态代理。那么就提供了一个代理无实现类的接口的可能。
8. 抽象类和接口的区别,类可以继承多个类么,接口可以继承多个接口么,类可以实现多个接口么。
特征 | 抽象类 | 接口 |
方法 | 可以有默认的方法实现 | JDK8以前所有的方法都是抽象的 |
实现 | extends | implements |
构造函数 | 可以有 | 不可以有 |
定义 | class | interface |
访问修饰符 | 抽象方法可以有public、protected、不写 | 默认是public,其他不可以 |
继承 | 单继承 | 多实现 |
main方法 | 可以有main方法,并且可以运行它 | 没有main方法 |
9. 继承和组合的区别在哪。
组合关系(Composition):部分和整体之间具有相同的生命周期,当整体消亡后,部分也将消亡。就像大雁的翅膀和大雁是组合关系。
聚合关系(Aggregation):部分与整体之间并没有相同的生命周期,整体消亡后部分可以独立存在。就像大雁和雁群是聚合关系。
【链接】---- 为什么说多用组合少用继承
组合 | 继承 |
不破坏封装,整体类与局部类之间松耦合,彼此相互独立 | 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现、缺乏独立性 |
具有较好的扩展性 | |
支持动态组合,运行时整体对象可以选择不同的局部变量 | |
整体类可以封装局部类,局部类的接口,提供新的接口 | 子类不能改变父类的接口 |
整体类不能自动获得和局部类同样的接口 | 子类能自动继承父类的接口 |
创建整体类的对象时,需要创建所有局部类的对象 | 创建子类的对象时,无需创建父类的对象 |
10. 重载Overload和重写Override的区别
11. 深拷贝和浅拷贝区别
【clone方法】
12. 基本数据类型及对应的包装类,Integer和int的区别
1、包装类是对象,拥有方法和字段,对象的调用都是通过引用对象的地址,基本类型不是
2、包装类型是引用的传递,基本类型是值的传递
3、声明方式不同,基本数据类型不需要new关键字,而包装类型需要new在堆内存中进行new来分配内存空间
4、存储位置不同,基本数据类型直接将值保存在值栈中,而包装类型是把对象放在堆中,然后通过对象的引用来调用他们
5、初始值不同,eg: int的初始值为 0 、 boolean的初始值为false 而包装类型的初始值为null
6、使用方式不同,基本数据类型直接赋值使用就好 ,而包装类型是在集合如 coolection Map时会使用
基本数据类型 | byte | short | int | long | char | float | double | boolean |
包装类 | Byte | Short | Integer | Long | Character | Float | Double | Boolean |
int x1 = 3;
Integer y1 = new Integer(3);
Integer y2 = new Integer(3);
System.out.println(x1 == y1); // true
System.out.println(y2 == y1); // false
// 在-128-127之间,从缓存中获取对象
Integer x2 = Integer.valueOf(23);
Integer x3 = Integer.valueOf(23);
System.out.println(x2 == x3);// true
// 大于127时, 重新创建对象
Integer x4 = Integer.valueOf(128);
Integer x5 = Integer.valueOf(128);
System.out.println(x4 == x5);// false
13. IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。
先说阻塞IO和非阻塞IO、同步IO和异步IO的区别
------------阻塞IO和非阻塞IO的两个概念是程序级别的。主要描述的是程序请求操作系统IO操作后,如果IO资源还没有准备好,那么程序该如何处理的问题:前者等待;后者继续执行(并且使用线程一直轮询,直到有IO资源准备好了)
------------同步IO和异步IO的两个概念是操作系统级别的。主要描述的是操作系统在收到程序请求IO操作后,如果IO资源没有准备好那么操作系统改如何响应程序的问题:前者不响应,直到IO资源准备好以后;后者返回一个标记(好让程序和自己知道以后的数据往哪里通知),当IO资源准备好以后,再用事件机制返回给程序。
数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
【链接】---- Java NIO
【链接】---- JAVA NIO详解(主要分析在网络编程中的应用)
NIO本身是基于事件驱动的思想来实现的,其目的就是解决BIO的大并发问题,在BIO模型中,如果需要并发处理多个I/O请求,那就需要多线程来支持,NIO使用了多路复用器机制,以socket使用来说,只有在socket有流可读或者可写时,应用程序才需要去处理它,在线程的使用上,就不需要一个连接就必须使用一个处理线程了,而是只是有效请求时(确实需要进行I/O处理时),才会使用一个线程去处理,这样就避免了BIO模型下大量线程处于阻塞等待状态的情景。
相对于BIO的流,NIO抽象出了新的通道(Channel)作为输入输出的通道,并且提供了缓存(Buffer)的支持,在进行读操作时,需要使用Buffer分配空间,然后将数据从Channel中读入Buffer中,对于Channel的写操作,也需要现将数据写入Buffer,然后将Buffer写入Channel中。
异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水例子中就是,这里的一排水壶换成了会响的水壶,水烧开之后,水壶会自动响来通知我水烧开了。
14. 什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。
【作用】
将对象持久化,对象序列化保存的是对象的”状态”,即它的成员变量。因此,对象序列化不会关注类中的静态变量。应用在网络传输,远程方法调用。
【相关知识】
1、在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。
2、通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化
3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致(就是private static final long serialVersionUID)
4、序列化并不保存静态变量。
5、要想将父类对象也序列化,就需要让父类也实现Serializable接口。
6、Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。
15. 内部类和匿名内部类
【内部类】一个类定义在另一个类的内部,内部类分为成员内部类和局部内部类;
【作用】
(1)成员内部类:定义在外部类的成员位置
class Outer
{
class Inner
{
//成员内部类
}
}
---------------------------------------------------------------------------------------------------------
(2)局部内部类:定义在外部类的方法里面
class Outer
{
public void method()
{
final int a = 10;
class Inner
{
System.out.println(a);
//局部内部类
}
}
}
---------------------------------------------------------------------------------------------------------
【匿名内部类】没有名字的内部类,在一个类中获取一个接口的实现类对象或者获取一个抽象类的子类对象
(1)作用:正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。
(2)匿名内部类的实现:第一种,继承一个类,重写其方法;第二种,实现一个接口(可以是多个),实现其方法。
(3)匿名内部类的注意事项
匿名内部类不能有构造方法。
匿名内部类不能定义任何静态成员、方法和类。
匿名内部类不能是public,protected,private,static。
只能创建匿名内部类的一个实例。
一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
匿名类和内部类中的中的this:有时候,我们会用到一些内部类和匿名类。当在匿名类中用this时,这个this则指的是匿名类或内部类本身。这时如果我们要使用外部类的方法和变量的话,则应该加上外部类的类名。
(4)匿名内部类的应用举例:比如多线程的实现上,实现多线程必须继承Thread类或是实现Runnable接口
public class Demo {
public static void main(String[] args) {
Thread t = new Thread() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
t.start();
}
}
public class Demo {
public static void main(String[] args) {
Runnable r = new Runnable() {
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
}
};
Thread t = new Thread(r);
t.start();
}
}
16. 四种引用:强引用、软引用、弱引用、虚引用。
【链接】 ---- Java的四种引用
------ 强引用(StrongReference):
Object o = new Object(); //强引用
强引用是最普遍的引用,如果一个对象具有强引用,那垃圾回收器绝不会回收它。即使内存空间不足时,JVM宁愿抛出OutOfMemoryError错误也不会回收强引用的对象,但是可以显式地设置o为null,这时gc就可以回收这个对象了。
------ 软引用(SoftReference):
String str = new String(“abc”); //强引用
SoftReference softRef = new SoftReference(str); //软引用
如果一个对象只有软引用,当内存空间足够时不会被回收,但是当内存空间不足时就会被回收。软引用会被应用在浏览器的后退按钮。
------ 弱引用(WeakReference):
String str = new String(“abc”);
WeekReference weekRef = new WeekReference(str);
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。垃圾收集器一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。
------- 虚引用(PhantomReference):
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
17. java8的新特性。
【链接】---- JDK8十大特性
(1)Lambda表达式和函数式接口
(2)接口的默认方法 —— 一个在接口里面有了一个实现的方法。
(3)方法引用 —— 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
(4)Stream API —— 新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
(5)Date Time API —— 加强对日期与时间的处理。
(6)Optional 类 —— Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
18. 在jdk1.5中,引入了泛型,泛型的存在是用来解决什么问题。
Java泛型设计原则:只要在编译时期没有出现警告,那么运行时期就不会出现ClassCastException异常.
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
【参数化类型】
【作用】
19. 简单描述正则表达式及其用途。
【java.util.regex】
【多个匹配器可以共享同一模式】
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaaab");
boolean b = m.matches();
【仅使用一次正则表达式的情况】
boolean b = Pattern.matches("a*b","aaaaaab");
21. final的作用
【链接】---- Java的final
22. 两个jar里面有同包名同名的类,如何区分调用?
包名类名都相同,那jvm没法区分了,一般ide是会提示发生冲突而报错的,如果不报错的,那就只有第一个包被引入(在classpath路径下排在前面的包),第二个包会在classloader加载类时判断重复而忽略。
23. JDK1.7中新增的资源释放管理
24. C++和Java的区别
1. Java没有指针概念。在C/C++中,指针操作内存经常会出现错误(指针悬空)
2. Java不支持多重继承,但允许多实现。虽然C++多继承功能强大,但使用复杂
3. Java除了基本数据类型以外,其他类型的数据都作为对象型数据
4. Java自动内存管理,不再需要手动删除。
5. Java不支持操作符重载,操作符重载被认为是C++的突出特征,不过Java还是可以通过类来实现操作符重载所具有的功能
6. Java 不支持 C++中的自动强制类型转换,如果需要,必须由程序显式进行强制类型转换。
7. Java中不提供goto语句
1. JVM内存模型
① Java栈(虚拟机栈):栈因为是运行单位,每个线程都有一个自己的Java栈,Java栈中存放的是一个个栈帧,每个栈帧对应的是一个被调用的方法,栈帧中存放的是局部变量表、操作数栈、指向运行时常量池的引用、方法返回地址和一些附加信息。线程当前执行的方法一定是位于Java栈的栈顶的。
② 本地方法栈:其作用和原理与Java栈十分相似,只不过本地方法栈是为本地方法服务的,而Java栈是为Java方法服务的。
③ 堆:用来存放用new创建的对象本身和数组的(当然,数组的引用是存放在Java栈中的),另外,堆是被所有线程共享的,在JVM中只有一个堆。
④方法区:也是被所有线程共享的,用来存放类的信息(类名、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码。
⑤程序计数器:程序计数器是每个线程私有的,如果线程执行的是非native方法,则程序计数器中存放的是当前需要执行的指令的地址,如果线程执行的是native方法,则程序计数器中的值是undefined。
2. java中堆和栈的区别
堆 | 栈 |
解决数据存储的问题,是存储的单位 | 解决程序的运行问题,是运行时的单位 |
用于存放用new创建的对象和数组 | 用于存放基本类型的数据变量和对象的引用变量(对象引用占4byte) |
先进先出,后进后出 | 先进后出,后进先出 |
动态分配内存大小,生存期也不必事先告知编译器 | 数据大小和生存期必须事先确定,缺乏灵活性 |
存取速度慢 | 存取速度快,仅次于寄存器,支持数据共享 |
3. JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存等。
从JDK1.5开始,java使用新的JSR-133内存模型,1.5版本基于happens-before规则对volatile语义进行了增强,阐述操作之间的可见性。
【重排序】:在执行程序时,为了提高性能,编译器和处理器会对指令做重排序;以i++为例
【内存屏障】:通过内存屏障可以禁止对指定操作的指令进行重排序,强制刷出缓存
【happens-before】:
【主内存和工作内存】:所有线程共享主内存,每个线程都有自己的工作内存,其中工作内存是cpu的寄存器和高速缓存的抽象描述。
4. 如何检测垃圾——引用计数法和可达性分析法
引用计数法:很难解决对象之间相互循环引用的问题
根对象集合(GC Roots)和一个对象之间有没有可达路径,GC Root的对象包括以下几种:
5. 基于分代的垃圾收集算法
【链接】---- Java 垃圾回收机制
新生代:分为Eden区和Survivor区,所有新创建的对象都存放在Eden区,当Eden区满后会触发minor GC,将Eden区和Survivor区中仍然存在的对象移到另一个Survivor区中,以保证有一个Survivor区是空的。另外,一般建议Young区的大小占整个堆的1/4,Eden:Survivor1:Survivor2= 8:1:1. 新生代对象存活率低,就采用复制算法
老年代:上面新生代中如果minor GC以后,Eden区和Survivor区仍然存在的对象在另一个Survivor区中存不下,那这些对象会直接存放到老年代中;另外,如果Survivor区中的对象足够老,也会直接放到老年代中;如果老年代也满了,则会触发Full GC,回收整个堆内存。老年代存活率高,就用标记清除算法或者标记整理算法
永久代:主要存放类的对象,如果一个类被频繁的加载,也可能会导致永久代满,永久代中的垃圾回收也是由Full GC触发的。
6. 你知道哪几种垃圾收集器,各自的优缺点,重点讲下cms和G1,包括原理,流程,优缺点。
【新生代】
第一代:Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
第二代:ParNew收集器 (复制算法): 新生代收并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;
第三代:Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;
第四代:G1收集器
【老年代】
第一代:Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
第二代:Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
第三代:CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。
第四代:G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
7. JVM垃圾收集器发展历程
第一阶段,Serial(串行)收集器
在jdk1.3.1之前,java虚拟机仅仅能使用Serial收集器。 Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
第二阶段,Parallel(并行)收集器
Parallel收集器也称吞吐量收集器,相比Serial收集器,Parallel最主要的优势在于使用多线程去完成垃圾清理工作,这样可以充分利用多核的特性,大幅降低gc时间。
第三阶段,CMS(并发)收集器
CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。
第四阶段,G1(并发)收集器
G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
8. g1和cms区别,吞吐量优先和响应优先的垃圾收集器选择。
【链接】---- 深入剖析JVM之G1收集器、回收流程
【1.G1收集器的最大特点】
【2.G1相比较CMS的改进】
【3.CMS和G1的区别】
【4.G1收集器的应用场景】
G1垃圾收集算法主要应用在多CPU大内存的服务中,在满足高吞吐量的同时,尽可能的满足垃圾回收时的暂停时间。
就目前而言、CMS还是默认首选的GC策略、可能在以下场景下G1更适合:
9. 当出现了内存溢出(OOM:OutOfMemoryError),怎么排错。
【链接】---- 内存溢出的几种原因和解决办法
java.lang.OutOfMemoryError:GC over head limit exceeded,系统高频的GC,但回收效果依然不佳的情况,就是产生了很多不可以被释放的对象,可能是引用不当或申请大对象导致的;
java.lang.OutOfMemoryError: PermGen space,系统的代码非常多或引用的第三方包非常多、或代码中使用了大量的常量、或通过intern注入常量、或者通过动态代码加载等方法,导致常量池的膨胀;
java.lang.StackOverflowError,栈溢出可以理解为创建的栈帧超过了栈的深度,最有可能的就是方法递归调用造成的,或者这个参数直接说明一个内容,就是-Xss太小了,使用参数-Xss去调整JVM栈的大小。
10. Java中什么时候会发生内存泄漏
【内存泄漏】有一些对象满足两个特点:对象是可达的,但是这些对象是无用的,即这些对象不会被GC回收,然而它们无用且占内存。
【根本原因】长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收。具体主要有以下几大类:
1. 静态集合类引起内存泄漏
像HashMap、Set等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有对象Object也不能被释放,因为这些对象一直被HashMap、Set等引用着。
2. 当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
Set set = new HashSet();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孙悟空","pwd2",26);
Person p3 = new Person("猪八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:3 个元素!
p3.setAge(2); //修改p3的年龄,此时p3元素对应的hashcode值发生改变
set.remove(p3); //此时remove不掉,造成内存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println("总共有:"+set.size()+" 个元素!"); //结果:总共有:4 个元素!
3. 各种连接
比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。
4. 单例模式
不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏。
【链接】---- Java中关于内存泄漏出现的原因汇总及如何避免内存泄漏(超详细版)
11. 怎么打出线程堆栈信息
第一步:在终端运行Java程序;
第二步:通过命令 jps找到已经启动的java进程的ID,选择需要查看的java程序的进程ID;
第三步:使用命令jstack PID 打印出java程序的线程堆栈信息;
【链接】---- 查看JVM信息的命令
12. 请解释如下jvm参数的含义:
-server -Xms512m -Xmx512m -Xss1024K 初始化堆和最大堆内存都为521m,可以防止jvm的堆内存动态扩容
-XX:PermSize=256m -XX:MaxPermSize=512m - 永久代最小内存256m,永久代最大扩展内存512m
-XX:MaxTenuringThreshold=20 新生代最大gc年龄为80
-XX:CMSInitiatingOccupancyFraction=80 设置老年代空间被使用80%后触发CMS gc
-XX:+UseCMSInitiatingOccupancyOnly。 只有当gc达到配置的阈值时才进行回收
-Xmn10M:新生代内存分配10M,剩余的交给老年代
-XX:SurvivorRatio=8:新生代中Eden区和Survivor区的内存比例为8:1
-XX:+HeapDumpOnOutOfMemoryError:堆内存溢出时Dump出当前的内存堆转储快照以便事后分析
13. JVM如何调优?
【JVM调优工具】JConsole(JDK自带)、VisualVM(JDK自带)、JProfile(商业软件)
1.堆信息查看
2.线程监控
线程数量、各个线程处于什么样的状态 —— 死锁检查
3.热点分析
1. 并发编程的几个挑战
【上下文切换的问题】
【死锁的问题】
【硬件和软件资源限制的问题】
2. 线程间通信
线程间的通信机制有两种:共享内存和消息传递
而Java线程之间的通信总是隐式进行的,采用的是共享内存模型,Java线程间的通信由Java内存模型(JMM)控制。
1. synchronized的原理是什么,解释以下名词:自旋锁,偏向锁,轻量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁。
【synchronized锁的三种表现形式】
【Synchronized实现原理】
【偏向锁】
偏向锁使用了一种等到竞争出现才释放锁的机制
2. volatile的原理,作用,能代替锁么。
—— 可见性:强制刷新缓存;volatile变量的happens-before规则(对一个volatile变量的写操作happens-before于读操作);
—— 有序性:内存屏障禁止指令重排;
—— volatile不是互斥的、不能代替锁,所以不能保证同一时刻只有一个线程访问共享变量,因此volatile也不会引起线程上下文的切换和调度,比synchronized的使用和执行成本更低。
【volatile的内存语义】
JSR-133增强了volatile的内存语义,严格限制编译器和处理器对volatile变量与普通变量的重排序,确保(volatile的写-读)和(锁的释放-获取)具有相同的内存语义。
【volatile和锁的原子性比较】
volatile仅仅保证对单个volatile变量的读/写具有原子性;
而锁的互斥执行的特性可以保证对整个临界区代码的执行具有原子性。
【volatile的使用优化】
使用场景:处理器的高速缓存行是64字节宽,且共享变量会被频繁地写;
举例:JDK 7的JUC包里新增了一个LinkedTransferQueue,它在使用volatile变量时,用一种追加字节的方式来优化出队和入队的性能。
理由:队列的头结点和尾结点都不足64字节,处理器会将它们读到同一个高速缓存行,而将头结点和尾结点都追加到64字节的话,可以避免它们加载到同一个缓存行,使头结点和尾结点不会互相锁定。
3. 用过读写锁(ReentrantReadWriteLock)吗,原理是什么,一般在什么场景下用。
state(int32位)
字段分成高16位与低16位,其中高16位表示读锁个数,低16位表示写锁个数4. Lock与Synchronized的区别 。
synchronized | Lock |
Java的关键字 | 是一个类 Lock lock = new ReentrantLock(); |
假设A线程获得锁,B线程会等待;此时如果A阻塞了,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式 |
1. 获取锁的线程执行完同步代码会自动释放锁; 2. 如果线程执行发生异常,JVM会让线程释放锁 |
在finally中必须释放锁unlock(),否则会造成线程死锁 |
不可判断锁的状态 | isLocked可以判断锁的状态 |
可重入锁,不可中断,非公平 | 可重入锁,可中断,公平与不公平 |
线程自旋和适应性自旋,锁消除,锁粗化,轻量级锁,偏向锁 | 读写锁ReentrantReadWriteLock |
5. 有哪些无锁数据结构,他们实现的原理是什么。
线程本地存储(线程局部变量)、CAS、CopyOnWrite、原子类、不变模式
6. ThreadLocal用过么,用途是什么,原理是什么,用的时候要注意什么。
【链接】---- Java并发编程:深入剖析ThreadLocal
7. CAS机制是什么,CAS实现原子操作的三大问题。
【原理】——有点像乐观锁
主存中保存V值,线程中要使用V值要先从主存中读取V值到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。
(1)ABA问题
如果一开始位置V得到的旧值是A,当进行赋值操作时再次读取发现仍然是A,并不能说明变量没有被其它线程改变过。有可能是其它线程将变量改为了B,后来又改回了A。
【ABA的解决办法】
在变量前面追加版本号:每次变量更新就把版本号加1,则A-B-A就变成1A-2B-3A。
从Java1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题,这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设定为给定的更新值。
(2)循环时间长开销大
(3)只能保证一个共享变量的原子操作
8. CopyOnWrite写时复制容器
【链接】---- 并发容器之CopyOnWrite
9. 用过哪些原子类,他们的原理是什么。
AtomicInteger、AtomicLong、AtomicBoolean、AtomicIntegerArray、AtomicLongArray.....
【一些具体的操作方法】
通过 AtomicInteger()或AtomicInteger(int num) 初始化:
getAndIncrement():原子递增。
getAndDecrement():原子递减。
getAndAdd(int num):原子性地将给定值添加到当前值.
主要在极高并发下使用,使用时不需要手动同步,Atomic包下的原子类都实现了CAS操作。
10. 不变模式
【定义】一个对象的状态在对象被创建之后就不再变化,这就是所谓的不变模式。
【弱不变模式】一个类的实例状态是不可变的,但是这个类的子类的实例状态可能会变化。
【强不变模式】一个类的实例状态不会改变,它的子类的实例状态也不会改变。
【使用场景】
1. 多线程的几种实现方式,什么是线程安全的
【链接】---- java多线程的实现方式
3. 用过线程池吗,如果用过,请说明原理,并说说newCache和newFixed有什么区别,构造函数的各个参数的含义是什么,比如coreSize,maxsize等。
【链接】---- 深入分析java线程池的实现原理
【链接】---- Java线程池的分析和使用
【使用场景】单个任务处理时间比较短,需要处理的任务数量较多
【好处】降低资源消耗,提高响应速度,提高线程的可管理性
【概述】Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行
【ThreadPoolExecutor的构造函数的参数含义】
【提交任务的两种方法】
【newFixedThreadPool和newCacheThreadPool的区别】见下表
newFixedThreadPool | newCacheThreadPool |
初始化指定一个线程数的线程池 | 初始化一个可以缓存线程的线程池 |
corePoolSize == maximumPoolSize,在初始化的时候指定 | coreSize=0,maxSize=Integer.MAX_VALUE |
LinkedBlockingQueue作为阻塞队列 | SynchronousQueue作为阻塞队列 |
当线程池没有可执行的任务时,也不会释放线程 | 当线程的空闲时间超过KeepAliveTime时,会自动释放线程资源 |
【阻塞队列】
【合理的配置线程池】首先分析任务的特性,从以下几个角度分析:
【饱和策略RejectedExecutionHandler】
【线程池的监控】
4. 线程池的关闭方式有几种,各自的区别是什么。
(1)shutdown关闭线程池
(2)shutdownNow关闭线程池并中断任务
6. 导致线程死锁的原因?怎么检测死锁?怎么预防线程死锁。
【链接】----- 死锁终极篇
【死锁定义】一组互相竞争资源的线程相互等待,导致“永久”阻塞的现象
【死锁的条件】
【死锁检测工具】
1、Jstack工具—— jstack是JVM自带的一种堆栈跟踪工具
2、JConsole工具 —— JConsole是JDK自带的监控工具
【规避死锁】并发程序一旦发生死锁,一般只能重启应用,因此,解决死锁的最好办法还是规避死锁。只要破坏上面其中一个死锁的条件,就可以成功避免死锁的发生。
/**********破坏“占有且等待”这个死锁条件***************/
class Allocator {
private List
/**********破坏“循环等待”这个死锁条件***************/
class Account {
private int id;
private int balance;
// 转账
void transfer(Account target, int amt){
Account left = this ①
Account right = target; ②
if (this.id > target.id) { ③
left = target; ④
right = this; ⑤
} ⑥
// 锁定序号小的账户
synchronized(left){
// 锁定序号大的账户
synchronized(right){
if (this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
7. 画一个线程的生命周期状态图。
8. sleep和wait的区别。
wait | sleep |
调用对象的wait方法,必须先获取对象锁 | 静态方法,不需要获取锁 |
进入到等待池 | 进入到阻塞状态 |
需要其他线程唤醒 | 到时自己醒 |
会释放锁资源 | 不会释放锁资源 |
9. sleep和sleep(0)的区别。
在线程中,调用sleep(0)可以释放cpu时间,让线程马上重新回到就绪队列而非阻塞队列,sleep(0)释放当前线程所剩余的时间片(如果有剩余的话),这样可以让操作系统切换其他线程来使用,提升效率。
10. 如何减少上下文切换
【背景】
【方法】
1. 信号量Semaphore,计数信号量
【链接】---- Java并发编程之Semaphore
2. 管程Monitor
3. CountDownLatch
countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别(比如countdownlatch的await方法和是怎么实现的)。
1. 生产者消费者模型
【实现方法】
5. 开启多个线程,如果保证顺序执行,有哪几种实现方式,或者如何保证多个线程都执行完再拿到结果。
用三个线程按顺序循环打印abc三个字母,比如abcabcabc。
【链接】---- 多个线程顺序执行的几种方法
【链接】----
(1)使用信号量Semaphore
(2)使用ReentrantLock
(3)join方法
(4)newSingleThreadExecutor只有一个线程的线程池
(5)newCachedThreadPool
(6)使用CountDownLatch
假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有10个线程同时调用它,如何做到。
spring的controller是单例还是多例,怎么保证并发的安全。
如果让你实现一个并发安全的链表,你会怎么做。
讲讲java同步机制的wait和notify。
多线程如果线程挂住了怎么办。
对AbstractQueuedSynchronizer了解多少,讲讲加锁和解锁的流程,独占锁和公平所加锁有什么不同。
简述ConcurrentLinkedQueue和LinkedBlockingQueue的用处和不同之处。
延迟队列的实现方式,delayQueue和时间轮算法的异同。
1. 说说你知道的几个线程安全的Java集合类:list、set、queue、map实现类。
【List】
CopyOnWriteArrayList:线程安全的List,在读多写少的场合性能非常好,远好于Vector,Vector和Stack也是线程安全的;
【Set】
ConcurrentArraySet:线程安全的Set;
【Queue】
ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做线程安全的 LinkedList,这是一个非阻塞队列。
【Map】
ConcurrentHashMap:线程安全的HashMap,HashTable也是线程安全的。
2. fail-fast机制
【定义】
【出现场景】
ArrayList,HashMap在单线程和多线程环境下都有可能出现
迭代器遍历时,在某一步迭代中remove一个元素;
一个线程迭代,另一个线程remove;
if (modCount != expectedModCount) throw new ConcurrentModificationException();
【避免fail-fast】
3. ArrayList和LinkedList有什么区别。
【ArrayList】
【LinkedList】
4. HashMap的源码,实现原理,底层结构。
【put函数大致的思路为】:
【get函数大致的思路为】:
【扩容是以2的次方扩充的】
5. JAVA8的ConcurrentHashMap底层原理,如何实现线程安全的?
【在java8之前】在jdk1.7中是采用Segment + HashEntry + ReentrantLock的方式进行实现的,使用分段锁来实现并发,数据结构为HashMap(数组加链表)的基础上再套一层segment数组,锁加在segment元素上,结构如下图。
【java8改进后的ConcurrentHashMap】1.8中放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现。
【链接】---- 浅谈Java8中的ConcurrentHashMap
【链接】---- Java8之ConcurrentHashMap实现原理
【链接】---- Java并发类ConcurrentHashMap内部机制
(1)几个重要的知识点
(2)几个重要原子操作
java8中还引入了Unsafe实例,用于获取/设置Node节点
// 获取节点i处的Node
static final Node tabAt(Node[] tab, int i) {
return (Node)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// 利用cas操作设置节点i处的Node
static final boolean casTabAt(Node[] tab, int i,
Node c, Node v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
// 设置节点i处的Node
static final void setTabAt(Node[] tab, int i, Node v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
(3)几个主要方法的实现原理
在没有并发的情况下,使用一个 baseCount volatile 变量就足够了,当并发的时候,CAS 修改 baseCount 失败后,就会使用 CounterCell 类了,会创建一个这个对象,通常对象的 volatile value 属性是 1。在计算 size 的时候,会将 baseCount 和 CounterCell 数组中的元素的 value 累加,得到总的大小,但这个数字仍旧可能是不准确的。
还有一个需要注意的地方就是,这个 CounterCell 类使用了 @sun.misc.Contended 注解标识,这个注解是防止伪共享的。是 1.8 新增的。使用时,需要加上 -XX:-RestrictContended
参数。
6. 用过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗?
Map类 | HashMap | LinkedHashMap | Treemap |
ConcurrentHashMap | HashTable |
数据结构 | 哈希散列表 | 继承了HashMap, 也是数组加链表的形式, 但是多了一个双向链表来 维护内部数据的顺序 |
红黑树 | 由Segment数组结构和 HashEntry数组结构组成 |
散列表 |
是否线程安全 | 线程不安全 | 线程不安全 | 线程不安全 (非同步) |
线程安全 (锁分段) |
线程安全 (synchronized) |
执行效率 | 高 | 高 | 低 | ||
默认容量, 负载因子, 扩容 |
16,0.75 当个数>容量*加载因子 新容量=2*原容量
|
11,0.75 新容量=2*原容量 + 1 |
|||
备注 | 运行null键/值 不保证有序 不保证顺序不变 |
遍历输出的顺序与put顺序一致; 按访问顺序输出; 同样支持null键/值 |
允许null键/值 保持key的大小顺序 中序遍历 |
不允许null键/值 |
7. 说出有顺序的Map实现类,他们是怎么保证有序的。
8. HashMap和HashTable的区别
HashMap | HashTable | |
继承 | 继承自AbstractMap | 继承自Dictionary,已被弃用 |
容量 | 初始容量是16,扩容一定是2的指数 | 初始容量是11,扩容是old*2+1 |
线程安全 | 非同步,线程不安全 | 同步,线程安全,对整个HashTable加synchronized |
null值 | 键/值允许null,但是null键只有一个, 因此HashMap不能由get()方法来判断是否存在某个键, 而是用containsKey()方法来判断。 |
键、值都不运行null |
遍历方式 | 多一个Enumeration的方式 | |
index | hashCode高16位和低16位进行异或运算得到hash, 对hash用&代替求模:hash & (n -1) |
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; |
12. 遍历并输出HashMap的key, value
//如果既需要输出key,也要输出value,则用:
Map map = new HashMap();
for(Entry entry:map.entrySet()){
entry.getKey();
entry.getValue();
}
//如果只需要输出key,则用:
Map map = new HashMap();
for(String key:map.keySet()){
system.Out.println(key)
}
9. TreeSet实现原理。
10. Java中的HashSet内部是如何工作的。
HashSet底层是基于HashMap来存储的,遍历table中的元素实现存储不重复元素:
如果hash值不同,说明是一个新元素,直接存储;
如果hash值相同,且equals方法判断相等,说明元素已经存在,不存;
如果hash值相同,且equals方法判断不相等,说明元素不存在,存。
13. 栈和队列的区别,各自的应用场景
【栈】先进后出
【链接】---- 栈的应用举例
【队列】先进先出
【链接】---- 消息队列应用场景
消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋,日志处理等问题;
使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ(淘宝),RocketMQ
应用解耦
在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦
异步消息
流量削锋
应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。
可以控制活动的人数
可以缓解短时间内高流量压垮应用
用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面
秒杀业务根据消息队列中的请求信息,再做后续处理
日志处理
日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下
日志采集客户端,负责日志数据采集,定时写受写入Kafka队列
Kafka消息队列,负责日志数据的接收,存储和转发
日志处理应用:订阅并消费kafka队列中的日志数据
14. Java中的队列都有哪些,有什么区别。
实现Queue的类 | 特点 | 数据结构 | 线程安全 |
PriorityQueue,优先级队列 | 保持队列的顺序是按照元素的大小重新排序的 | 数据结构是堆,底层是用数组保存数据 | 不安全 |
Deque,双端队列 | 既可以当成双端队列使用,也可以被当成栈来使用 | ||
ArrayDeque循环队列 |
用数组实现的Deque | 不安全 |
【ArrayBlockingQueue数组实现的线程安全的有界的阻塞队列】
【链接】---- Java并发包--ArrayBlockingQueue
1. 什么是Servlet?
Servlet是用来处理客户端请求并产生动态网页内容的Java类。在无状态的HTTP协议下管理状态信息。
2. Servlet运行过程。
3. Servlet的生命周期,是否有线程安全问题,如何解决?
【生命周期】
【是否有线程安全问题】
一个Servlet实例会同时服务于多个客户端请求,则多个线程同时执行同一个实例的同一个service方法,由于java的内存模型,servlet实例变量不正确的使用会导致线程不安全。
【解决方法】
-----实现SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。
-----同步代码块synchronized,被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。
-----避免使用实例变量是保证Servlet线程安全的最佳选择,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。
4. Servlet的体系结构
与Servlet主动关联的是三个类:ServletConfig、ServletRequest和ServletResponse;
其中,ServletConfig在Servlet初始化时就传递给Servlet了,后两个是在请求到达时调用Servlet传递过来的
5. 乱码问题
【响应乱码】
//指定响应数据的格式
response.setContenetType("text/html;charset=utf-8");
【请求乱码】
//告诉服务器,以指定的编码解析请求的数据
request.setCharacterEncoding("指定编码");
方案一:解码,将ISO8859-1的编码转换成指定的编码
name = new String(name.getBytes("ISO8859-1"),"UTF-8");
方案二:修改tomcat默认字符集
【jdbc乱码】?useUnicode=true&characterEncoding=utf-8
【数据库乱码】
建表时指定搜索引擎以及字符集:engine = Innodb default charset=utf8;
【页面乱码】
6. 页面跳转——转发与重定向
【重定向】
【转发】
重定向 | 转发 |
地址栏发生了变化 | 地址栏没有发生改变 |
重新发送了一次请求 | 没有发生新的请求,服务器内部控制权的转移 |
没有共享请求与响应 | 共享请求与响应 |
无法访问WEB-INF下的资源 | 可以访问WEB-INF下的资源 |
可以跨域 | 不可以跨域 |
7. Request对象的主要方法
setAttribute(String name,Object):设置名字为name的request的参数值
getAttribute(String name):返回由name指定的属性值
getAttributeNames():返回request对象所有属性的名字集合,结果是一个枚举的实例
getCookies():返回客户端的所有Cookie对象,结果是一个Cookie数组
getCharacterEncoding():返回请求中的字符编码方式
getContentLength():返回请求的Body的长度
getHeader(String name):获得HTTP协议定义的文件头信息
getHeaders(String name):返回指定名字的request Header的所有值,结果是一个枚举的实例
getHeaderNames():返回所以request Header的名字,结果是一个枚举的实例
getInputStream():返回请求的输入流,用于获得请求中的数据
getMethod():获得客户端向服务器端传送数据的方法
getParameter(String name):获得客户端传送给服务器端的有name指定的参数值
getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例
getParameterValues(String name):获得有name指定的参数的所有值
getProtocol():获取客户端向服务器端传送数据所依据的协议名称
getQueryString():获得查询字符串
getRequestURI():获取发出请求字符串的客户端地址
getRemoteAddr():获取客户端的IP地址
getRemoteHost():获取客户端的名字
getSession([Boolean create]):返回和请求相关Session
getServerName():获取服务器的名字
getServletPath():获取客户端所请求的脚本文件的路径
getServerPort():获取服务器的端口号
removeAttribute(String name):删除请求中的一个属性
8. 什么是Servlet链(Servlet Chaining)?
Servlet链是把一个Servlet的输出发送给另一个Servlet的方法。第二个Servlet的输出可以发送给第三个Servlet,依次类推。链条上最后一个Servlet负责把响应发送给客户端。
9. Filter如何工作?
在Servlet的service方法调用之前调用,FilterChain代表当前的整个请求链,所以通过调用FilterChain.doFilter可以将请求 继续传递下去。
1、懒汉式,线程不安全,lazy初始化
2、懒汉式,线程安全,lazy初始化,效率很低
3、饿汉式,线程安全,不是lazy初始化,效率高但是浪费内存
4、双检锁方式,线程安全,lazy初始化
5、静态内部类,线程安全,lazy初始化
6、枚举式,线程安全,不是lazy初始化,防止利用反射强制重复构建对象,可以反序列化创建对象
【应用】
InputStreamReader和OutputStreamWriter,是从字节到字符的转换桥梁
【应用】
BufferedInputStream是具体的装饰器实现者,它给InputStream类附加了功能
【应用】
桥接模式也是拥有双方,同样是使用接口(抽象类)的方式进行解耦,使双方能够无限扩展而互不影响,其实二者还是有者明显的区别:
【应用】
Filter是基于责任链模式设计的
把抽象和实现解藕,于是接口和实现可在完全独立开来。
JDBC
StringBuilder.append
Guava缓存创建对象
slf4j日志接口