Java面试所需的知识

目录

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. 计算机网络

(1)网络7层架构

1. 说一说OSI七层模型

第七层:应用层——为应用程序提供服务,最小单位——apdu;

  • HTTP超文本传输协议
  • FTP文件传输协议
  • POP3邮局协议的第三个版本
  • SMTP简单邮件传输协议
  • Telnet远程登录协议
  • DNS域名解析协议

第六层:表示层——确保一个系统的应用层发送的消息可以 被另一个系统的应用层读取,加密解密、转换翻译、压缩解压缩;最小单位——ppdu;

第五层:会话层——不同的机器上的用户之间建立、管理、维护对话;

第四层:传输层——定义一些传输数据的协议和端口。传输协议同时进行流量控制,或是拥塞控制,解决传输效率与能力的问题,保证这些数据段有效到达对端;最小单位——tpdu;

  • TCP传输控制协议
  • UPD用户数据报协议

第三层:网络层——控制子网的运行,如逻辑编址、分组传输、路由选择;最小单位——分组(包)报文;

  • IP(IPv4,Ipv6)网络之间互连的协议
  • ICMP网络控制消息协议
  • ARP地址解析协议

第二层:数据链路层——物理寻址,同时将原始比特流转变为逻辑传输线路;最小单位——帧;

  • WiFi,GPRS,ZigBee,帧中继

第一层:物理层——定义物理设备的标准,主要对物理连接方式,电气特性,机械特性等制定统一标准,传输比特流;最小单位——比特流。

  • IEEE 802.1A, IEEE 802.2到IEEE 802.11

Java面试所需的知识_第1张图片


2. 说一说TCP/IP四层协议

Java面试所需的知识_第2张图片

(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请求(我可以建立连接吗?),服务器就会回应一个ACK+SYN(可以+请确认)。而真实的IP会认为,我没有发送请求,不作回应。服务器没有收到回应,会重试3-5次并且等待一个SYN Time(一般30秒-2分钟)后,丢弃这个连接。
  • 如果攻击者大量发送这种伪造源地址的SYN请求,服务器将会消耗大量的资源来处理这种半连接(保存和遍历IP列表),何况还要不断对这个IP进行SYN+ACK的重试

【SYN Flood防御】

  • cookie源认证
  • reset认证
  • TCP首包丢弃
  • TCP代理

【链接】---- 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连接(四元组、五元组、七元组)

Java面试所需的知识_第3张图片

  • 源、目标端口号:TCP协议通过使用"端口"来标识源端和目标端的应用进程。端口号可以使用0到65535之间的任何数字。
  • 顺序号字段:用来标识从TCP源端向TCP目标端发送的数据字节流,它表示在这个报文段中的第一个数据节。  
  • 确认号字段:只有ACK标志为1时,确认号字段才有效。它包含目标端所期望收到源端的下一个数据字节。  
  • 头部长度字段:给出头部占32比特的数目。没有任何选项字段的TCP头部长度为20字节;最多有60字节的TCP头部。  
  • 标志位字段(U、A、P、R、S、F):占6比特。各比特的含义如下:  

  ◆URG:紧急指针(urgent pointer)有效。  

  ◆ACK:确认序号有效。  

  ◆PSH:接收方应该尽快将这个报文段交给应用层。  

  ◆RST:重建连接。  

  ◆SYN:发起一个连接。  

  ◆FIN:释放一个连接。  

  • 窗口大小字段:此字段用来进行流量控制。单位为字节数,这个值是本机期望一次接收的字节数。  
  • TCP校验和字段:对整个TCP报文段,即TCP头部和TCP数据进行校验和计算,并由目标端进行验证。  
  • 紧急指针字段:它是一个偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。  
  • 选项字段:可能包括"窗口扩大因子"、"时间戳"等选项。

【四元组】源IP地址、目的IP地址、源端口、目的端口

【五元组】源IP地址、目的IP地址、协议号、源端口、目的端口

【七元组】源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型以及接口索引


9. TCP的拆包粘包问题,TCP的最大连接数,TCP攻击

【拆包粘包定义】

TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的流水,是连成一片的,其间并没有分界线。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

【解决方案】

  1. 消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;
  2. 在包尾增加回车换行符进行分割,例如FTP协议;
  3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;
  4. 更复杂的应用层协议。

【TCP的最大连接数】

【链接】---- 单服务器最大tcp连接数及调优

  • client最大tcp连接数:client每次发起tcp连接请求时,除非绑定端口,通常会让系统选取一个空闲的本地端口(local port),该端口是独占的,不能和其他tcp连接共享。tcp端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535,所以在全部作为client端的情况下,最大tcp连接数为65535,这些连接可以连到不同的server ip。
  • server最大tcp连接数:server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。
  • 实际的tcp连接数:在实际环境中,受到机器资源、操作系统等的限制,特别是sever端,其最大并发tcp连接数远不能达到理论上限。在unix/linux下限制连接数的主要因素是内存和允许的文件描述符个数(每个tcp连接都要占用一定内存,每个socket就是一个文件描述符),另外1024以下的端口通常为保留端口。对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万 是没问题的。

10. TCP/IP协议的可靠性,以及它的脆弱性。

【TCP/IP协议的可靠性】

  1. 面向连接,服务器和客户端在彼此交换数据之前必须先建立一个TCP三次握手连接
  2. 应用数据被分割成TCP认为最适合发送的数据块
  3. 超时重发
  4. 当TCP收到发自TCP连接另一端数据,它将发送一个确认。
  5. TCP将保持它首部和数据的检验和
  6. 对失序数据进行重新排序,然后才交给应用层(TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序)
  7. 对于重复数据,能够丢弃重复数据(IP数据报会发生重复)
  8. 流量控制——滑动窗口协议
  9. 字节流服务

【链接】-------  TCP协议如何保证传输可靠性

【TCP/IP的脆弱性】

(1)不能提供可靠的身份验证

  1. TCP/IP 协议以 32 bit 的 IP 地址来作为网络节点的唯一标识,而 IP 地址只是用户软件设置中的一个参数,因而是可以随意修改的;
  2.  由于TCP/IP 不能对节点上的用户进行有效的身份认证,服务器无法鉴别登录用户的身份有效性,攻击者可以冒充某个可信节点的 IP 地址,进行 IP 欺骗攻击;
  3. 其次,由于某些系统的 TCP 序列号是可以预测的,攻击者可以构造一个TCP'数据包,对网络中的某个可信节点进行攻击。

(2)不能有效防止信息泄露

  1. IPv4 中没有考虑防止信息泄漏,在 IP 、 TCP 、 UDP 中都没有对数据进行加密;
  2. IP 协议是无连接的协议,一个 IP 包在传输过程中很可能会经过很多路由器和网段,在其中的任何一个环节都很容易进行窃昕 。

(3)没有提供 ----可靠的信息完整性验证---- 手段

  1. 在 IP 协议中,仅对 IP 头实现校验和保护;
  2. 在UDP 协议中,对整个报文的校验和检查是一个可选项,并且对 UDP 报文的丢失不做检查;
  3. 在 TCP 协议中,虽然每个报文都经过校验和检查,并且通过连续的序列号来对包的顺序和完整进行检查,保证数据的可靠传输。但是,校验算法中没有涉及加密和密码验证,很容易对报文内容进行修改,再重新计算校验和。

(4)没有手段控制资源占用和分配

  1. TCP/IP 中,对资源占用和分配设计的一个基本原则是自觉原则;
  2. 如参加 TCP通信的一方发现上次发送的数据报丢失,则主动将通信速率降至原来的一半。这样,也给恶意的网络破坏者提供了机会:如网络破坏者可以大量的发 IP 报,造成网络阻塞,也可以向一台主机发送大量的 SYN 包从而大量占有该主机的资源 (SYN Flood) 。

11. TCP的流量控制和拥塞控制

【链接】---- TCP的流量控制和拥塞控制

流量控制】流量控制是作用于发送者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失

【如何实现流量控制】由滑动窗口协议实现,主要的方法是接收方返回的ACK中包含自己的接收窗口大小,并且利用大小来控制发送方的数据发送。

【流量控制引发的死锁】如果发送者收到了一个窗口为0的应答,发送者会停止发送,等待接收者的下一次应答;但是如果接收者发送了一个窗口不为0的数据在传输过程中丢失了,发送者会一直等待下去,而接收者以为发送者已经收到了应答,等待接收新的数据,这样双方就相互等待,从而产生了死锁。

【避免死锁的发生】TCP为每一个连接设有一个持续计时器。每当发送者收到一个窗口为0的ACK应答后就启动该计时器,时间一到就主动询问接收者的窗口大小;如果接收者仍然返回零窗口,则重置该计时器继续等待;如果窗口不为0,则表明应答报文丢失了,发送方重置发送窗口后开始发送,这样就避免了死锁的产生。

拥塞控制】拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况

常用的方法是:(1)慢开始、拥塞避免;(2)快重传、快恢复

  • 拥塞窗口cwnd(congestion window):拥塞窗口的大小取决于网络的拥塞程度,并且在动态的变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接收方的接收能力,发送窗口可能小于拥塞窗口
  • 一个传输轮次所经历的时间及时往返时间RTT
  • 慢开始门限ssthresh状态变量

【慢开始】不要一开始就发送大量的数据,先探测一下网络的拥塞程度,即由大到小逐渐增加拥塞窗口的大小。每经历一个传输轮次,拥塞窗口cwnd就加倍。为防止cwnd增长过大引起网络拥塞,设置了一个慢开始门限ssthresh。

  • 当cwnd < ssthresh 时,使用慢开始算法;
  • 当cwnd > ssthresh 时,使用拥塞避免算法;
  •  当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

 

(3)HTTP原理 

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开头表示服务器的一些错误。

 — —涉及到的协议有:

  • 应用层使用了HTTP协议进行超文本传输;
  • 对于服务器后台处理应该有telnet远程调用协议响应用户;
  • DNS协议获取网络地址,即IP地址;
  • 打开网页,网页显示用到了表示层的HTML协议
  • 另外必然用到了传输层的TCP网络层的IP协议网络层ARP协议获取物理地址;ICMP协议控制信息的传递。

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)长连接

  • http1.0需要使用keep-alive参数来告知服务器端要建立一个长连接
  • 而http1.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管线化)

  • 管线化机制通过持久连接(persistent connection)完成,仅 HTTP/1.1 支持此技术(HTTP/1.0不支持)
  • 只有 GET 和 HEAD 请求可以进行管线化,而 POST 则有所限制
  • 初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议
  • 管线化不会影响响应到来的顺序,如上面的例子所示,响应返回的顺序并未改变
  • HTTP /1.1 要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可
  • 由于上面提到的服务器端问题,开启管线化很可能并不会带来大幅度的性能提升,而且很多服务器端和代理程序对管线化的支持并不好,因此现代浏览器如 Chrome 和 Firefox 默认并未开启管线化支持

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的加密方式是什么,讲讲整个加密解密流程。

【介绍】

  • Http直接通过明文在浏览器和服务器之间传递消息,容易被监听抓取到通信内容。 
  • Https采用对称加密和非对称加密结合的方式来进行通信。 
  • Https不是应用层的新协议,而是Http通信接口用SSL和TLS来加强加密和认证机制。

【对称加密和非对称加密的区别】

  • 对称加密速度快,非对称加密速度慢。
  • 对称加密要将密钥暴露,和明文传输没区别。
  • 非对称加密将公钥暴露,供客户端加密,服务端使用私钥解密。

【Https加密】—— 对称加密和非对称加密结合方式

  1. 浏览器使用Https的URL访问服务器,建立SSL链接。
  2. 服务器收到SSL链接,发送非对称加密的公钥A返回给浏览器
  3. 浏览器生成随机数,作为对称加密的密钥B
  4. 浏览器使用公钥A,对自己生成的密钥B进行加密,得到密钥C
  5. 浏览器将密钥C,发送给服务器。
  6. 服务器用私钥D对接受的密钥C进行解密,得到对称加密钥B。
  7. 浏览器和服务器之间可以用密钥B作为对称加密密钥进行通信。

【总结】这样浏览器和服务器就共享一个对称加密密钥B,重要的是不会被拦截到。只在传输密钥B的时候进行了一次非对称加密,之后就用对称加密进行传送数据。


25. Http和Https的三次握手有什么区别。

【Https的握手过程】

1,客户端输入https网址,链接到server443端口;

2,服务器手中有一把钥匙和一个锁头,把锁头传递给客户端。数字证书既是公钥,又是锁头

3,客户端拿到锁头后,生成一个随机数,用锁头把随机数锁起来(加密),再传递给服务器。这个随机数成为私钥,现在只有客户端知道

4,服务器用钥匙打开锁头,得到随机数。该随机数变成了新的锁头,把内容锁起来(加密),再传递给客户端。这个随机数服务器也知道了,并且用它来加密数据

5,客户端用自己手中的钥匙(随机数),解密后得到内容。客户端用私钥解密数据

6,接下来的客户端和服务器的数据交换都通过这个随机数来加密。只有客户端和服务器知道私钥,所以数据传输是安全的,不是明文的

【两者区别】

  1. Https协议需要到CA申请证书或自制证书
  2. Http是直接和TCP进行数据传输,而Https经过一层SSL加密
  3. Http是明文传输,Https通过SSL加密,更安全
  4. Http的端口号是80,Https的端口号是443

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协议简介

【简介】

  • ICMP网络控制消息协议,位于第三层网络层;用于在IP主机、路由器之间传递控制消息
  • 控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息
  • 这些控制消息并不传输用户数据,但是对用户数据的传递起着重要的作用
  • ICMP报文是在IP报文内部的

【典型应用】

  • Ping—— 检测网络连通性
  • Tracert—— 跟踪报文经过的每一个节点,检测网络丢包及时延的有效手段

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”。

  • A记录就是把一个域名解析到一个IP地址(Address,特制数字IP地址),而CNAME记录是把域名解析到另外一个域名;
  • CNAME将几个主机名指向一个别名,其实跟指向IP地址是一样的,因为这个别名也要做一个A记录的;
  • 使用CNAME记录可以很方便地变更IP地址,服务器变更IP时,只需要变更别名的A记录;
  • A记录在输入域名时不用输入WWW.来访问网站,CNAME记录是必须有。

(4)加密算法

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表示私钥
	}  

2. 数据结构

1.B-树、B+树

利用索引查询的时候不能将所有索引一次性加载到内存,一般是逐一加载磁盘页,这里的磁盘页对应着索引树的节点;

二叉查找树查询的时间复杂度是O(logN),最坏情况下,磁盘IO的次数等于树的高度;

因此为了减少磁盘IO次数,需要将原本瘦高的树变得矮胖。

【B-树】—— 多路平衡查找树,它的每一个节点最多包含m个孩子,m被称为B树的阶,而m的大小取决于磁盘页的大小

可以将原本“瘦高”的树结构变得“矮胖”,一个m阶的B树具有如下的特征:

  1. 根节点至少有两个子女;
  2. 每个中间节点都包含k-1个元素和k个孩子,其中m/2 <= k <= m;
  3. 每一个叶子节点都包含k-1个元素,其中m/2 <= k <= m;
  4. 所有的叶子节点都位于同一层;
  5. 每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划

Java面试所需的知识_第4张图片

【B+树】由于B+树的中间节点没有存放数据,同样大小的磁盘页能放更多的节点元素,更加“矮胖”,查询时磁盘IO的次数变少

  • 有k个子树的中间节点也有k个元素(B树中是k-1个元素),每个元素不保存数据。只用来索引,数据都保存在叶子节点
  • 所有的叶子节点中包含了全部元素的信息,且叶子结点本身依关键字的大小自小而大顺序链接。(叶子节点用指针连接
  • 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。

Java面试所需的知识_第5张图片

在数据库的聚集索引中,叶子节点直接包含数据。在非聚集索引中,叶子节点带有指向数据的指针。

【B-数与B+树的比较】

B-树查找只要找到匹配元素的节点即可,匹配元素可能处于中间节点,也可能处于叶子节点,所以的查找性能并不稳定

B+树比B-树的优势:每次都得查询到叶子节点,所以B+树查询性能稳定;磁盘IO次数更少;范围查询更简便。


2. 红黑树

【链接】----  红黑树的实现

【红黑树的应用场景】TreeSet、JDK1.8的HashMap底层数据结构用的红黑树

【红黑树的规则】

  1. 每一个节点都只能是红色或黑色
  2. 根节点是黑色
  3. 每个叶节点(NIL空节点)是黑色的
  4. 如果一个节点是红色的,那它的两个子节点都是黑色的
  5. 从任一节点到其每个叶子节点的所有路径都包含相同数目的黑色节点

【红黑树各种操作的时间复杂度】能保证在最坏情况下,均为O(logn)

【为什么使用红黑树】

相较于AVL树,红黑树对平衡性的要求没有AVL树那么高,牺牲了一定的平衡性,但是也是近似平衡的;同时AVL树为了维持高度平衡,每次插入、删除操作比较复杂、耗时,使用AVL的代价比较高;所以红黑树是在查询性能和维护平衡的开销上做了一个平衡,使得插入、删除、查找各种操作性能都比较稳定,均为O(logn)。

相较于哈希表,权衡三个因素: 查找速度, 数据量, 内存使用,可扩展性,有序性。

hash查找速度会比红黑树快,而且查找速度基本和数据量大小无关,属于常数级别;而红黑树的查找速度是log(n)级别。并不一定常数就比log(n) 小,因为hash还有hash函数的耗时。当元素达到一定数量级时,考虑hash。但若你对内存使用特别严格, 希望程序尽可能少消耗内存,那么hash可能会让你陷入尴尬,特别是当你的hash对象特别多时,你就更无法控制了,而且 hash的构造速度较慢。

  1. 红黑树是有序的,Hash是无序的,根据需求来选择。
  2. 红黑树占用的内存更小(仅需要为其存在的节点分配内存),而Hash事先应该分配足够的内存存储散列表,即使有些槽可能弃用
  3. 红黑树查找和删除的时间复杂度都是O(logn),Hash查找和删除的时间复杂度都是O(1)。

【左旋】左旋中的左意味着被旋转的结点将变成一个左结点

右旋的操作和左旋的代码是对称的,左旋和右旋的时间复杂度均为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
   }

【插入】

  1. 将红黑树当做一颗二叉查找树,将结点插入
  2. 将插入的结点着色为“红色”(为了不违背特性5)
  3. 通过一系列的旋转着色,使之重新成为一颗红黑树(想办法满足特性4)

3. List

  • ArrayList 线性表结构(一段地址连续的内存空间),访问效率高,插入和删除效率低
  • LinkedList 双向链表结构,访问效率低,插入和删除效率高
  • Vector 是List中唯一一个线程安全的,但是现在已经不怎么用
  • List list = new ArrayList<>();
  • list.add("aa");
  • list.remove(0);  或者   list.remove("aa");
  • int index = list.indexOf("aa");

4. 栈(Stack)—— 先进后出

  • Stack stack = new Stack<>();
  • 入栈stack.push("元素1");
  • 出栈stack.pop();
  • 查看栈顶部的元素,但是不从栈中移除它stack.peek();

5. 队列(Queue)—— 先进先出

  • LinkedList类实现了Queue接口,所以可以把LinkedList当成Queue来用;Queue queue = new LinkedList<>();
  • 入队queue.offer("aaa1");
  • 出队queue.poll();
  • 取队首元素queue.peek();

【非阻塞队列】

实现Queue的类 特点 数据结构 线程安全
Deque,双端队列 既可以当成双端队列使用,也可以被当成栈来使用    
PriorityQueue,优先级队列 保持队列的顺序是按照元素的大小重新排序的 数据结构是堆,底层是用数组保存数据 不安全

ConcurrentLinkedQueue

增加、删除O(1),查找O(n) 基于链表 安全

【阻塞队列】线程阻塞时,不是直接添加或者删除元素,而是等到有空间或者元素时,才进行操作。

实现Queue的类 特点 数据结构 使用场景
DelayQueue 其中的元素只有当其指定的延迟时间到了才能取到,插入数据不会阻塞,只有取数据才会阻塞 没有大小限制的队列 管理一个超时未响应的连接队列
SynchronousQueue   内部没有容器的队列 可以缓存线程的线程池

newCachedThreadPool

的阻塞队列
PriorityBlockingQueue   基于优先次序的队列

 

LinkedBlockingQueue   基于链表的无界队列

只有一个线程的线程池

newSingleThreadExecutor

的阻塞队列

ArrayBlockingQueue   基于数组的有界队列  

6. 哈希散列表

【hash冲突解决的方法】

  1. 开放定址法:线性探测再散列,二次探测再散列,伪随机探测再散列
  2. 再哈希法
  3. 链地址法
  4. 建立公共溢出区:将哈希表分为基本表溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

7. 图

【图的表示方法】邻接矩阵、邻接表、逆邻接表、十字链表

【图的深度优先遍历DFS】回溯法

【图的广度优先遍历BFS】

8. 位图

 


3. 算法

1. 递归和迭代的区别

递归:调用自己的编程方法,即自己调用自己,把一个大型的复杂的问题转化为一个与原问题相似的规模较小的问题来解决,使用递归的时候需要注意的两点:

  1. 必须在函数中调用自己
  2. 必须有递归出口来终止递归过程

迭代:利用变量的原值推算出变量的一个新值,如果递归是自己调用自己的话,,迭代就是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

(1) Java算法

2.  八大排序

快排空间复杂度最优O(logn),最差O(n)

快排优化:基准元素的选取,采用random随机获得一个下标,将该下标对应的元素与递归数组第一个元素交换

Java面试所需的知识_第6张图片

【稳定性】

  • 如果待排数组中有两个数Ai = Aj,他们在排序前和排序后位置顺序相同,则能保证稳定性。
  • 稳定的排序有:冒泡,插入,归并,基数排序

【复杂度】

  • 满足平均时间复杂度= O(nlogn)的算法:快排,Shell,堆排,归并
  • 满足空间复杂度= O(1)的算法:冒泡,插入,Shell,选择,堆排

【Arrays.sort实现原理】

【链接】---- Arrays.sort底层原理

如果元素个数少于47这个阀值,就用插入排序;

如果元素个数少于286这个阈值,就进入快排;

如果超过286,进入伪归并。

3. 二叉树有关算法

【链接】---- 20道题搞定BAT二叉树算法题

(1)求二叉树中的节点个数

【递归解法】

  • 如果二叉树为空,节点个数为0;
  • 如果不为空,二叉节点个数 = 左子树节点个数 + 右子树节点个数 + 1。

(2)求二叉树的最大层数(最大深度)

【递归解法】

  • 如果二叉树为空,深度为0;
  • 如果不为空,最大深度 = max(左子树深度,右子树深度) + 1。

(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. 拓扑排序——有向无环图

拓扑排序主要用来解决有向图中的依赖解析问题;

  1. 初始化一个int[] inDegree保存每一个结点的入度;
  2. 选取入度为0的每一个结点,将其指向的结点的入度减一;
  3. 最后如果拓扑排序的结点个数等于图的顶点数,则存在拓扑排序,即当前的图是有向无环图。

【链接】---- LeetCode 207.课程表 Java实现

5. 动态规划

数塔取数问题、编辑距离、矩阵取数问题、01背包问题

【链接】---- 动态规划算法学习笔记 Java实现

6.贪心算法

【存在的问题】

  • 不能保证求得的最后解是最佳的;
  • 不能用来求最大最小解问题;
  • 只能在某些特定条件约束的情况下使用,例如贪心策略必须具备无后效性等。

【应用领域】

旅行商问题、马踏棋盘、背包、装箱等

7.回朔算法

 

8.Dijkstra算法

 

9.深度优先和广度优先

 

(2)海量数据处理

一亿个数求第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的频度排序。


4. 操作系统

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低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善。


5. MySQL数据库

【链接】---- 互联网公司面试必问的Mysql题目

Java面试所需的知识_第7张图片

1、事务

--开启事务:(两种)
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存储引擎


2、数据库结构和锁

5. Mysql框架结构

Java面试所需的知识_第8张图片

在 InnoDB 存储引擎中,所有的数据都被逻辑地存放在表空间中,表空间(tablespace)是存储引擎中最高的存储逻辑单位,在表空间的下面又包括段(segment)、区(extent)、页(page);

【表空间】

  • 在Innodb存储引擎中,表空间分为默认的公共表空间文件和每一个对应的自己的独立表空间文件;
  • 独立表空间文件所存储的内容主要就是B+树(索引),一个表可以有多个索引,也就是在一个文件中,可以存储多个索引,而如果一个表没有索引的话,用来存储数据的被称为聚簇索引,索引文件由段(segment),簇(extends)(有的文章翻译为区),页面(page)组成。

【段】

  • 表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等;
  • 数据段即为B+树的叶子节点;
  • 索引段即为B+树的非叶子节点;
  • 创建一个索引(B+树)时会同时创建两个段,分别是内节点段和叶子段,内节点段用来管理(存储)B+树非叶子(页面)的数据,叶子段用来管理(存储)B+树叶子节点的数据;
  • 因此,在索引数据量一直增长的过程中,所有新的存储空间的申请,都是从“段”这个概念中申请的。

【区/簇】

  • 区/簇是由64个连续的页组成的,每个页大小为16KB,即每个簇的大小为1MB;
  • 一个簇是物理上连续分配的一个段空间,每一个段至少会有一个簇,在创建一个段时会创建一个默认的簇;
  • 一个段所管理的空间大小是无限的,可以一直扩展下去,但是扩展的最小单位就是簇。

【页】

  • 页是InnoDB磁盘管理的最小单位;
  • 在逻辑上(页面号都是从小到大连续的)及物理上都是连续的;
  • 在向表中插入数据时,如果一个页面已经被写完,系统会从当前簇中分配一个新的空闲页面处理使用;
  • 如果当前簇中的64个页面都被分配完,系统会从当前页面所在段中分配一个新的簇,然后再从这个簇中分配一个新的页面来使用;
  • 单次从磁盘读取单位是页,而不是行,也就是说,你即便只读取一行记录,从磁盘中也是会读取一页的,当然了单页读取代价也是蛮高的,一般都会进行预读。

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的使用及原理详解

  • MVCC——多版本的并发控制(Mutil-Version Concurrency Control),乐观锁的一种实现方式;
  • MVCC主要适用于可提交读、可重复读的隔离级别;
  • MVCC的优点是:读不加锁,读写不冲突;
  • 每行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号,每次数据更新时都更新版本号;
  • 修改时Copy出当前版本随意修改,各个事务之间无干扰。
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback);
  • 但由于Mysql的写操作会加排他锁,so从某种意义上来说,Mysql的MVCC并非真正的MVCC,只是借用MVCC的名号实现了读的非阻塞而已。

8. 举一个数据库死锁的例子,mysql怎么解决死锁。

----例:事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。事务A和事务B在相互等待对方的资源释放,就是进入了死锁。当出现死锁以后,有两种策略:

  1. 超时退出:通过参数设置innodb_lock_wait_timeout超时时间,当第一个线程超时退出,然后其他线程继续执行
  2. 发起死锁检测:将参数innodb_deadlock_detect设置为on,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务继续执行。

----减少死锁的主要方向,就是控制访问相同资源的并发事务量;

----如何尽可能避免:

  • 以固定的顺序访问表和行,即按序申请锁资源;
  • 大事务拆小,大事务更倾向于死锁;
  • 在同一个事务中,一次性锁定所需要的所有资源,减少死锁概率;
  • 降低隔离级别;
  • 为表添加合理的索引。如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

//9. MySQL的锁

【根据加锁的范围分】全局锁、表级锁、行锁

【全局锁】

  • 对整个数据库实例加锁,使用“flush tables with read lock”来加全局读锁;
  • 使用场景是做全库逻辑备份,使用此锁全库处于完全只读状态;
  • 弊端:如果在主库上备份,在备份期间都不能进行更新,业务基本得停摆;如果在从库上备份,会导致主从延迟;
  • 做逻辑备份的其他方法:使用自带的逻辑备份工具mysqldump,在其中使用-single-transaction参数,但是前提条件是使用事务引擎的库

【表级锁】一共有两种:表锁,元数据锁(MDL)

----表锁-----:语法是lock tables ... read/write

----元数据锁-----:不需要显示使用,在访问一个表的时候会被自动加上,当对一个表做增删改查操作的时候,会加上MDL读锁,当要对表结构变更的时候,会加上MDL写锁;读锁之间不互斥,可以多线程同时对一个表做增删改查;读写锁之间、写锁之间是互斥的。事务中 的MDL锁,在语句执行开始时申请,但是在语句执行完以后不会马上释放,而会等到事务提交后再释放

----如何安全地给小表加字段-----:因为加字段就是对表结构做更改,会自动加上MDL写锁。 首先要解决长事务问题,事务不提交,就会一直占着MDL锁,就拿不到MDL写锁以进行加字段操作。另外,如果是热点表,增删改查的请求很频繁,只能在alter table 语句里设定等待MDL写锁的时间,如果能够在这个时间里拿到写锁最好,如果拿不到也不要阻塞后面的业务语句,先放弃。之后开发人员再重试命令重复这个过程。

【行锁】在innodb事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放的,而是要等到事务结束时才释放,这个就是两阶段锁协议


3、索引

10. Mysql的索引原理,索引的类型有哪些,如何创建合理的索引,索引如何优化。

【链接】-------  MySQL性能调优——索引详解与索引的优化

【链接】-------  索引基础——B-Tree、B+Tree、红黑树、B*Tree数据结构

【索引的优缺点】

— —优点:

  • 可以大大缩短查询时间,这也是创建索引的最主要的原因,提高系统的性能
  • 如果能够在排序分组操作中利用好索引,同样可以显著减少查询中分组和排序的时间

— —缺点:

  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
  • 索引需要占物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
  • 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护。

— —判断是否应该建索引的条件:

1、较频繁的作为查询条件的字段应该创建索引

2、唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件

3、增、删、改操作较多的数据库字段不适合建索引

4、数据量大的表才适合建立索引

— —索引使用的注意事项:

  1. 索引用在WHERE和ORDER BY命令上涉及的列,最左前缀匹配原则,可根据EXPLAIN来查看是否用了索引还是全表扫描;
  2. 尽量选择区分度高的列作为索引,区分度的格式是:count(distinct col)/count(*);
  3. 定义有外键的数据列一定要建立索引;
  4. 尽量避免更新聚集索引数据列,因为聚集索引数据列的顺序就是表记录的物理存储顺序;
  5. 模糊查询时,使用like前面以百分号开头会使索引失效;
  6. 尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描如:select id from t where num is null 可以在num上设置默认值0,使num列没有null值,然后这样查询:select id from t where num = 0;
  7. 尽量避免在where子句中使用!=或><操作符,否则将导致引擎放弃使用索引而进行全表扫描;
  8. 尽量避免在where子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num = 10 or num = 20;可以这样查询: select id from t where num = 10 union all select id from t where num = 20;
  9. in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3) ;对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3;
  10. 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间。

— —聚集索引和非聚集索引的区别:

根本区别是表记录的物理顺序与索引的排列顺序是否一致

聚集索引 非聚集索引
表记录的物理顺序与索引的排列顺序一致,查询效率快 非聚集索引叶节点仍然是索引节点,即数据记录的地址,二次查询问题
修改慢,修改的时候会根据索引列的排序对数据页重排 非聚集索引层次多,不会造成数据重排

【索引的类型】普通索引,唯一索引,主键索引,全文索引,复合索引

— —普通索引(index)

  • 基本的索引,它没有任何限制
  • 如果是char,varchar类型,length可以小于字段实际长度;
  • 如果是BLOB和TEXT类型,必须制定length
//标准语句:
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千万级数据处理

  • 索引优化;
  • 读写分离:经典的数据库拆分方案,主库负责写,从库负责读;
  • 垂直分库分表——可以做表拆分,减少单表字段数量,优化表结构;
  • 水平分库分表;
  • 查询缓存:使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;
  • 参数调优;
  • 服务器设置优化、升级硬件。

Java面试所需的知识_第9张图片

— —怎么优化全表扫描?

  1. 要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,最左前缀匹配原则,可根据EXPLAIN来查看是否用了索引还是全表扫描;
  2. 索引列不能参与计算,保持列“干净”;
  3. 尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描如:select id from t where num is null 可以在num上设置默认值0,使num列没有null值,然后这样查询:select id from t where num = 0;
  4. 尽量避免在where子句中使用!=或><操作符,否则将导致引擎放弃使用索引而进行全表扫描;
  5. 尽量避免在where子句中使用or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:select id from t where num = 10 or num = 20;可以这样查询: select id from t where num = 10 union all select id from t where num = 20;
  6. in 和 not in 也要慎用,否则会导致全表扫描,如:select id from t where num in(1,2,3) ;对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3;
  7. like模糊查询也会导致全表扫描,在 where 子句中使用参数,也会导致全表扫描。

12.怎么看执行计划,如何理解其中各个字段的含义

【链接】------ MySQL优化系列

【链接】------  优化 sql 语句的一般步骤

-----执行计划:

Java面试所需的知识_第10张图片

-----各字段的含义:

  • select_type:表示select类型,常见的取值有simple、primary、union、subquery;
  • table:输出结果集的表;
  • type:表示mysql在表中找到所需行的方式,或者叫访问类型,由差到最好依次是all、index、range、ref、eq_ref、const、system、null;
  • possible_keys:表示查询时可能使用的索引;
  • key:表示实际使用的索引;
  • key_len:使用到索引字段的长度
  • rows:扫描行的数量;
  • Extra:执行情况的说明和描述,包括不适合在其他列中显示但是对执行计划非常重要的额外信息

4、情景题、优化题 

13. 高并发下,如何做到安全的修改同一行数据。

  1. 使用悲观锁,Mysql提供了两种方法:select ... for update和lock in share mode;
  2. 使用FIFO缓存队列的思路,直接将请求放入队列中,但是高并发下因为请求很多,会将队列内存“撑爆”;
  3. 使用乐观锁,增加版本标识字段或时间戳字段。

14. 如何避免长事务对业务的影响?

答:这个问题应该从应用开发端和数据库端分别来看。

【应用开发端】

  • 确保事务是自动提交的,如果不是设置——set autocommit = 1;
  • 去掉不必要的只读事务;
  • 根据业务本身的预估,通过SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行时间太长。

【数据库端】

  • 设置长事务阈值,超过就报警/kill
  • 测试阶段,提前输出所有的general_log,分析日志行为提前发现问题
  • 如果Mysql版本高于5.6,把innodb_undo_tablespaces设置为2(或更大的值),即使真的出现大事务导致回滚过大,清理也会更方便一点

15.数据库自增主键可能的问题。

  1. 不可拆库,因为主键id会重复
  2. 当innodb_autoinc_lock_mode为0时候, 自增id都会连续,但是会出现表锁的情况
  3. 把innodb_autoinc_lock_mode 设置为1,甚至是2,会提高性能,但是会在一定的条件下导致自增id不连续。

16.MYSQL的主从延迟怎么解决。

  1. 在架构上做优化,尽量让主库的DDL快速执行
  2. 从库不需要太高的数据安全,可以将sync_binlog设置为0或者关闭binlog,innodb_flushlog也可以设置为0以提高sql的执行效率
  3. 从库使用比主库更好的硬件设备

17. 你做过的项目里遇到分库分表了吗,怎么做的,有用到中间件么,比如sharding jdbc等,他们的原理知道么。

主要两种拆分,垂直拆分和水平拆分

【垂直拆分】减少单表字段数量,优化表结构

【水平拆分】

  • 拆分规则
  1. RANGE从 0到10000一个表,10001到20000一个表;
  2. 取id字段,对其hash取模,分配到不同的数据库上;
  3. 按地理区域拆分,比如手机号码;
  4. 按时间拆分,冷热数据分离。
  • 分库分表后面临的问题
  1. 事务支持 分库分表后,就成了分布式事务了。
  2. 跨库join 分两次查询实现,在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
  3. 跨节点的count,order by,group by以及聚合函数问题 与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
  4. 数据迁移,容量规划,扩容等问题 
  5. ID问题 一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。

【分库分表中间件】

  • 基于代理方式的有MySQL Proxy和Amoeba;
  • 基于Hibernate框架的是Hibernate Shards;
  • 基于jdbc的有当当sharding-jdbc;
  • 基于mybatis的类似maven插件式的有蘑菇街TSharding;
  • 通过重写spring的ibatis template类的Cobar Client。

—— IDBC驱动版的优点:

  1. 轻量,范围更加容易界定,只是 JDBC 增强,不包括 HA、事务以及数据库元数据管理
  2. 开发的工作量较小,无需关注 nio,各个数据库协议等
  3. 运维无需改动,无需关注中间件本身的 HA
  4. 性能高,JDBC 直连数据库,无需二次转发
  5. 可支持各种基于 JDBC 协议的数据库,如:MySQL,Oralce,SQLServer

—— Proxy版的优点:

  1. 可以负责更多的内容,将数据迁移,分布式事务等纳入 Proxy 的范畴
  2. 更有效的管理数据库的连接
  3. 整合大数据思路,将 OLTP 和 OLAP 分离处理

【Sharding-JDBC】

  • 设计理念的主要流程:SQL解析->SQL改写->SQl路由->SQL执行->结果归并;
  • 基于 JDBC 接口的扩展,是以 jar 包的形式提供轻量级服务的

【链接】---- 分库分表


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行数据?

  1. 直接执行limit 10000,单个语句占用时间长,锁的时间也较长,而且大事务会导致主从延迟
  2. 在一个连接中循环执行20次limit 500

19.参数调优

  • wait_timeout:数据库连接闲置时间(长连接),闲置连接会占用内存资源。可以从默认的8小时减到半小时。
  • max_user_connection:最大连接数,默认为0(无上限),最好设一个合理上限。
  • thread_concurrency:并发线程数,设为CPU核数的两倍。
  • key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like 'key_read%',保证key_reads / key_read_requests在0.1%以下最好。
  • innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询 show status like 'Innodb_buffer_pool_read%',保证 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests 越高越好。
  • read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能。
  • sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小。

5、琐碎知识

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的前提下,要求所有非主属性完全依赖于候选码,不能产生部分依赖

Java面试所需的知识_第11张图片

         举例:上面这个表的候选码(姓名,课程),但是教材只依赖于课程,不是完全依赖;

3)第三范式:在2NF的前提下,要求所有非主属性直接依赖于候选码,不能产生传递依赖

         举例:老师职称依赖于老师,老师依赖于候选码,这是传递依赖

4)BC范式:在3NF的前提下,主属性不依赖于主属性

5)第四范式(4NF):要求把同一表内的多对多关系删除

6)第五范式(5NF):从最终结构重新建立原始结构


22.ER图(Entity-Relationship Approach)

Java面试所需的知识_第12张图片

  • 用矩形表示实体
  • 用椭圆表示实体的属性,下划线表示为主键属性
  • 用菱形表示实体之间的联系,同时在无向边旁标上联系的类型(1:1,1:n或m:n)

23. mysql中in 和exists 区别。

  1. in语句是把外表和内表作hash连接,而exists语句是对外表做loop循环,每次loop循环再对内表进行查询
  2. 如果查询的两个表大小相当,用in和exists差别不大
  3. 如果两个表一个较小,一个是大表,子查询表大的用exists,子查询表小的用in
  4. not exists比not in快

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个结果集排序。
 


6、视图

为什么需要视图?

对于同一张表,A用户只关新部分字段,B用户只关心另一些字段,那么此时把全部的字段都显示给他们看,是不合理的。

所以我们需要提供了一个表的“视图”,给到他们想看的数据的同时,屏蔽掉其它字段的数据;

使用视图可以简化复杂的sql操作,隐藏具体的细节,保护数据;

但是性能较差。

视图的是怎么实现的?

(1)视图是一种虚表,向视图提供数据内容的语句为 SELECT 语句,可以将视图理解为存储起来的 SELECT 语句

(2)视图没有存储真正的数据,真正的数据还是存储在基表中,一个基表可以有0个或多个视图

(3)视图中,用户可以使用SELECT语句查询数据,也可以使用INSERT,UPDATE,DELETE修改记录

7、存储过程和触发器

【存储过程】

一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数,就像java的方法一样,可以重复的被调用。

create procedure proc1    --创建一个存储过程
@para1 varchar(50),@para2 varchar(50)
as 
begin

     --在存储过程中处理SQL
     select * from bank
end

优点:

  1. 对代码进行封装,功能强大,调用方便;
  2. 将数据处理放在数据库服务器上,减少业务系统与数据库的交互;
  3. 存储过程是一个预编译的代码块,执行效率高;

缺点:

  1. 如果存储过程中有大量的复杂运算,会占用数据库服务器的CPU,造成数据库服务器的压力;
  2. 不同数据库的存储过程语法不一致,难以通用和维护;
  3. 业务逻辑放在数据库中,难以迭代;
  4. 过多地使用存储过程会降低系统的可移植性。

【触发器】

由事件来触发某种操作,触发器经常用于加强数据的完整性约束和业务规则等。 触发器创建语法四要素:

  1. 监视地点:table;
  2. 监视事件:insert/update/delete;
  3. 触发时间:before/after;
  4. 触发事件:insert/update/delete。

触发器基本语法:

create trigger triggerName  
after/before insert/update/delete on 表名  
for each row   --这句话在mysql是固定的  
begin  
    sql语句;  
end;  

摒弃触发器!触发器的功能基本都可以用存储过程来实现。 

8、约束 

MySQL支持以下约束:主键约束、外键约束、唯一约束、非空约束

 【主键约束】一个表只能有一个主键约束(索引)

--添加主键约束
alter  table 表名 add primary key (列名);   --可以有多个列名

【外键约束】

alter table 表名称 add foreign key (列名称)  references  关联表名称(列名称);

【唯一约束】只能唯一不能重复

alter table 列名 add unique(列名称);   --可以有多个列名称,用逗号隔开。

【非空约束】

alter table 表名 modify 列名 列类型 not null; 

6. Redis数据库

1. Redis是什么。简述它的优缺点。

  • 它是一种支持Key-Value等多种数据结构的内存数据库(Remote Dictionary Server);
  • 整个数据库统统加载在内存中进行操作,定期通过异步操作把数据库flush到硬盘上进行持久化保存;
  • 可基于内存,亦可持久化,支持网络;
  • 单线程;
  • 数据自动过期;
  • 分布式;
  • 一种数据结构服务器,值可以是String、list、Hash、sets和sorted sets等类型;
  • 可用于缓存,事件发布或订阅,高速队列;
  • 缺点:数据库容量受到物理内存的限制,不能用作海量数据的高性能读写。

2. Redis相比memcached有哪些优势?

  • memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型;
  • Redis的速度比memcached快;
  • Redis可以持久化数据;
  • Redis是单线程,多路复用方式提高处理效率,而memcached是多线程的,通过CPU线程切换来提高处理效率。

3. Redis有哪几种数据淘汰策略?

达到最大内存限制(maxmemory)时,Redis 根据 maxmemory-policy 配置的策略, 来决定具体的行为。

  • noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。 大多数写命令都会导致占用更多的内存(有极少数会例外, 如 DEL )。
  • allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
  • volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
  • allkeys-random: 所有key通用; 随机删除一部分 key。
  • volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
  • volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。

4. Redis使用的优化和注意事项

【使用Key值前缀来作命名空间】虽然说Redis支持多个数据库(默认32个,可以配置更多),但是除了默认的0号库以外,其它的都需要通过一个额外请求才能使用。在使用前缀作为命名空间区隔不同key的时候,最好在程序中使用全局配置来实现。

【注意垃圾回收】在Redis中保存数据时,一定要预先考虑好数据的生命周期(使用Expire设置过期时间)。但是自动过期有一个问题,很有可能导致你还有大量内存可用时,就让key过期去释放内存,或者是内存已经不足了key还没有过期。你可以用一个ZSET来维护你的数据更新程度,你可以用时间戳作为score值,每次更新操作时更新一下score,这样你就得到了一个按更新时间排序序列串,快速找到最老的数据。

【Redis的Sharding机制】 学习

【单线程】

【缓存穿透】

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。

【缓存雪崩】

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决:

  1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  2. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。

【缓存击穿】

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决:

  1. 设置热点数据永远不过期。
  2. 加互斥锁,第一个线程进去获取到锁并去数据库取值,其他的线程等锁;能针对每个key设置颗粒级的锁会更好,这样key1不会影响到key2。

5. 事务

不支持回滚!

Redis事务相关的命令:MULTI、EXEC、DISCARD和WATCH;

【用法】

  • 使用 MULTI 命令进入事务模式.这个命令只会返回OK。此时用户就可以发出多个要一起执行的命令了。 Redis暂时不会执行这些命令,而是把它们放进队列。 当 EXEC 被调用时,所有的命令才会被一次性执行;
  • 相反的,如果调用了 DISCARD 命令,则会清除事务队列中的所有命令,然后退出事务;

【Redis事务错误处理】

  • Redis事务使用时可能会出现两种错误,一种是在命令排队时因为命令语法错误或者内存不足等,这种错误可以通过检查命令的返回值发现错误,返回QUEUED表示入队成功,否则是失败,客户端判断出错误一般做法是终止事务;
  • 还有一种是在EXEC调用后,一些命令执行失败,这种情况即使有命令执行失败,队列中的其他命令也会被执行,这是因为,对于应用层面的错误,并不是redis自身需要考虑和处理的问题。

Redis事务保证以下两点

  • 事务中的所有命令都是按顺序执行的。在一个客户端执行Redis事务的过程中,不会接收其他任何客户端对它发出的请求;
  • 所有的命令要么都被一起处理,要么全都没有被处理,所以Redis事务是原子的。

为什么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应用场景

  • String(字符串)
  1. 使用场景:常规key-value缓存应用;常规计数: 微博数, 粉丝数;短信验证码;
  2. 一个字符串类型的值能存储最大容量是512M;
  3. SET key value  设置指定key的值;
  4. GET key  获取指定key的值;
  5. GETRANGE key start end 返回key中字符串的子字符;
  6. GETSET key value 将给定key的值设为value,并返回key的旧值(old value);
  7. STRLEN key 返回key所储存的字符串的长度;
  8. INCR key 将key中储存的数字值增一;
  9. DECR key 将key中储存的数字值减一;
  10. APPEND key value 如果 key 已经存在并且是一个字符串, 将指定的 value 追加到该 key 原来值(value)的末尾。
  • List(列表)
  1. 使用场景:既可以作为栈,又可以作为队列;消息队列系统;取最新N个数。
  2. 按照插入顺序排序,可以在列表的头部或者尾部添加元素,一个列表最多可以包含(2的32次方-1)个元素 (4294967295, 每个列表超过40亿个元素)。
  3. LLEN key 获取列表的长度。
  4. LINDEX key index 通过索引获取列表中的元素。
  5. LPUSH key VALUE1.. VALUEN 将一个或多个值插入到列表头部,返回值为列表的长度。如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作。当 key 存在但不是列表类型时,返回一个错误。
  6. LPOP key 移出并获取列表的第一个元素,返回值为移除的元素。
  7. RPUSH key VALUE1.. VALUEN 将一个或多个值插入到列表尾部,返回值为列表的长度。如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。当 key 存在但不是列表类型时,返回一个错误。
  8. RPOP key 移出并获取列表的最后一个元素,返回值为移除的元素。
  • Hash(哈希)
  1. 使用场景:存储部分变更数据,如用户信息、新闻详情等。
  2. hash 是一个string类型的field和value的映射表,hash特别适合用于存储对象。
  3. 每个 hash 可以存储 232 - 1 键值对(40多亿)。
  4. HSET key filed value 将哈希表 key 中的字段 field 的值设为 value。
  5. HMSET key FIELD1 VALUE1 ...FIELDN VALUEN  用于同时将多个 field-value (字段-值)对设置到哈希表中,此命令会覆盖哈希表中已存在的字段;如果哈希表不存在,会创建一个空哈希表,并执行 HMSET 操作。
  6. HGET key filed  获取存储在哈希表中指定字段的值。
  7. HMGET KEY_NAME FIELD1...FIELDN 用于返回哈希表中,一个或多个给定字段的值;如果指定的字段不存在于哈希表,那么返回一个 nil 值。
  8. HGETALL key  获取在哈希表中指定 key 的所有字段和值。
  9. HEXISTS key filed  查看哈希表 key 中,指定的字段是否存在。
  10. HDEL key FIELD1 VALUE1 ...FIELDN  删除一个或多个哈希表字段。
  • Set(集合)
  1. 使用场景:交集,并集,差集;共同关注、共同喜好、二度好友等功能。
  2. Set 是 String 类型的无序集合,不可重复。
  3. 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
  4. SADD key VALUE1..VALUEN 将一个或多个成员元素加入到集合中,已经存在于集合的成员元素将被忽略。
  5. SCARD key  获取集合的成员数
  6. SMEMBERS key  返回集合中的所有成员
  7. SPOP key 移除并返回集合中的一个随机数
  8. SINTER key KEY1..KEYN 返回给定所有给定集合的交集
  9. SUNION key KEY1..KEYN 返回给定所有给定集合的并集
  • sorted set(有序集合)
  1. 使用场景:优先级队列;显示最新的项目列表;排行榜,取TOP N操作;计数;
  2. 每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
  3. 有序集合的成员是唯一的,但分数(score)却可以重复。
  4. 集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。
  5. ZADD key SCORE1 VALUE1.. SCOREN VALUEN 用于将一个或多个成员元素及其分数值加入到有序集当中。
  6. ZCARD key  获取有序集合的成员数

8.  Redis的使用场景

(1)缓存——两种方式保存数据

方案一:读取前,先去读取Redis,如果没有数据,读取数据库,并将数据拉入Redis;

  1. 避免缓存击穿(缓存无法命中,每次请求都要击穿到后端数据库系统查询,使数据库压力过大,甚至使数据库被压死);
  2. 数据的实时性相对会差一点,适用于对于数据实时性要求不是特别高的场景。

方案二:插入数据时,同时写入Redis。

  1. 数据实时性强;
  2. 但是开发时不便于同一处理;
  3. 适用于数据量不大的数据存储。

(2)计数器

  1. 诸如统计点击数等应用。由于单线程,可以避免并发问题;
  2. 命令:INCRBY。

(3)新评论列表

【具体实现】

  • 假设数据库中的每条评论都有一个唯一的递增的ID字段,每次新评论发表时,将它的ID添加到一个redis 列表中:
LPUSH latest.comments 
  • 将列表裁剪为指定长度,因此Redis只需要保存最新的5000条评论:
LTRIM latest.comments 0 5000
  • 每次需要获取最新N条评论时:
LRANGE latest.comments 0 N

(4)排行榜

  • 每次获得新得分时:
ZADD key  
  • 得到前100名高分用户:
ZREVRANGE key 0 99
  • 得到用户的排名:
ZRANK key 

(5)分布式锁与单线程机制

(6)队列


7. Git

Java面试所需的知识_第13张图片

【基础知识】

  • Workspace工作区,Stage/Index暂存区,Repository仓库区(或本地仓库),Remote远程仓库
  • HEAD指针指向的版本是当前版本
  • Git的版本回退就是调整HEAD指针指向的版本
  • 每一次commit都有一个commit id,可以通过这个id恢复到某一个版本
  • git commit只负责把暂存区的修改提交到历史记录区,也就是说每次在工作区的修改,如果不用git add到暂存区,那这些修改就不会commit到历史记录区中。

【常用命令】

  • git init 把当前目录变成Git仓库
  • git add   把文件添加到暂存区中
  • git rm 把文件从版本库中删除,最后还要git commit
  • git commit -m 把暂存区中的所有内容提交到历史记录区,-m后面的是本次提交的说明
  • git status 查看仓库当前的状态
  • git diff 查看文件修改的地方
  • git log 显示提交记录,从最近到最远
  • git reflog 用来记录你的每一次命令
  • git reset  既可以版本回退,也可以把暂存区的修改回退到工作区
  • git branch 创建分支
  • git branch 查看当前分支
  • git checkout 切换到某分支
  • git merge 把branch1分支合并到当前分支上
  • git push origin master 把本地master分支的最新内容推送到远程库
  • git reset HEAD 把文件在暂存区的修改回退到工作区 

【git reset ,git checkout,git revert的区别】

在版本回退方面:

git checkout --  把文件在工作区的修改、删除全部撤销(“一键还原”),让文件在工作区的内容回退到最近一次git add或git commit的状态

git reset 将HEAD指向的位置改变为之前的某个目标版本,但是reset后,目标版本之后的版本不见了

git revert 通过创建一个新的版本,这个版本的内容与我们要回退到的目标版本一样,但是HEAD指针指向这个新生成的版本;如果我们想恢复之前的某一版本的同时,又想保留该版本后面的版本,记录下这整个版本变动的流程,就可以用这个方法。

在分支方面:

git checkout 切换到某分支

【fetch,merge,pull的区别】

  • git fetch 从远程库获取最新版本到本地,不会自动merge
  • git merge 将内容合并到当前分支
  • git pull相当于git fetch和git merge,即更新远程仓库的代码到本地仓库,然后将内容合并到当前分支

【tag——定义一个有意义的名字(比如v1.2)与某个commit绑在一起】

  • git tag 打一个标签
  • git tag 查看所有标签
  • git show v1.2  查看标签为v1.2的信息
  • git tag -d v1.2 将标签v1.2删除
  • git push origin v1.2 将标签v1.2推送到远程库

8. Linux

1. 基本操作指令

  • 当前目录【./】
  • 上一级目录【../】
  • 主目录【~/】
  • 切换目录用【cd】
  • 查看当前路径【pwd】
  • 列出指定目录中的内容【ls】

---------------------------------------------------------------------------------

  • 查看当前进程【ps】
  • 怎么执行退出【exit】
  • 退出当前命令【ctrl+c】彻底退出
  • 执行睡眠【ctrl+z】挂起当前进程,恢复后台

----------------------------------------------------------------------------------

  • 创建目录【mkdir】
  • 创建文件【cat >> file】、【vi】、【touch】
  • 复制文件【cp 源文件 目标文件】
  • 移动文件【mv 源文件 目标文件】
  • 删除文件【rm】

------------------------------------------------------------------------------------

chmod设置文件权限,后面跟三个数字,分表表示不同用户或用户组的权限,比如【chmod 777】

  • 第一个数字表示文件所有者的权限u
  • 第二个数字表示与文件所有者同属一个用户组的其他用户的权限g
  • 第三个数字表示其他用户组的权限o
  • 权限分为三种:可读(r=4),可写(w=2),可执行(x=1)
  • 综合起来:可读可执行(rx=4+1=5),可读可写(rw=4+2=6),可读可写可执行(rwx=4+2+1=7)

------------------------------------------------------------------------------------

2. Linux网络配置常用命令

【链接】---- Linux网络配置常用命令

  • 查看或设置网络接口——ifconfig
  1. ifconfig 得到当前系统的网络配置情况
  2. ifconfig eth3 显示网卡eth3的信息
  3. ifconfig eth0 192.168.1.3  设置第一块网卡的ip地址为192.168.1.3
  4. ifconfig eth0 netmask 255.255.0.0   设置第一块以太网网卡的子网掩码为255.255.0.0
  5. ifconfig eth0 up/down  启用/禁止第一块以太网网卡
  6. ifconfig eth0 -arp 禁止第一块以太网网卡的ARP协议(减号表示禁止)
  • 查看或设置主机名——hostname
  1. 直接输入hostname   显示主机名.DNS域名
  2. hostname -d  显示主机的DNS域名
  3. hostname -i   显示主机的IP地址
  4. hostname -a  显示主机的别名
  • 查看或设置路由表——route
  1. route add -net 192.168.76.0 netmask 255.255.255.0 dev eth0   //将192.168.76.0添加到路由表中,子网掩码为255.255.255.0,使用/dev/eth0设备;
  2. route del -net 192.168.76.0 netmask 255.255.255.0 dev eth0    //将192.168.76.0从路由表中删除,子网掩码为255.255.255.0,使用/dev/eth0设备;
  3. route add -net 10.0.0.0 netmask 255.0.0.0 reject   //添加一个拒绝路由,目标网络为10.0.0.0,子网掩码为255.255.255.0
  • 查看或配置arp缓存——arp
  1. IP数据包常通过以太网发送,但以太网设备并不识别32位IP地址,而是以48位以太网地址传输以太网数据包。因此,必须把ip目的地址换成以太网目的地址。
  2. arp命令处理系统的arp缓存,它可以清除缓存中的地址映射,建立新的地址映射。
  3. arp -v   //显示ARP缓存详细信息
  • 查看网络状态——netstat
  • 检测网络主机——ping
  • 追溯路由——traceroute
  • 域信息搜索器——dig
  • IP计算器——ipcalc
  • 监视网络状态——netreport

9. 位运算技巧

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

   【资源】--- 阿里面试题总结

面向对象设计原则:

  • 高低原则(高内聚低耦合);
  • 开闭原则(对扩展开、对修改关闭);
  • 里氏替换原则(子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法)
  • 依赖倒置原则(高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象)
  • 单一原则

面向对象特性:封装、继承、多态

怎么理解Java的多态?

  1. 多态,简而言之就是同一个行为具有多个不同表现形式或形态的能力。
  2. 多态的分类:运行时多态和编译时多态。
  3. 运行时多态的前提:继承(实现),重写,向上转型
  4. 向上转型与向下转型。
  5. 继承链中对象方法的调用的优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。

1. 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方法:克隆一个对象

  1. 类必须实现Clonable接口,访问修饰符为public;
  2. 是浅克隆。

getClass方法:返回对象的运行时类的class对象

finalize方法:不需要程序员手动调用,当对象被释放时,由gc调用finalize

equals方法:用于比较两个对象是否相等

  1. equals如果没有被重写,那么和==没有区别;
  2. 如果重写了equals,那么按照重写的比较规则进行比较;
  3. 重写equals方法时,必须同时重写hashCode方法
  4. 自反性、对称性、传递性、一致性。

hashCode方法:返回对象的hashCode值(如果没有重写,返回的是对象的内存地址)

toString方法:将对象转换为字符串表示形式

  1. 在没有被重写的情况下,toString 返回的值是: // 对象所在类的包名.类名@hashCode的16进制表示形式

4. hashCode和equals方法。

(1)hashCode方法

【hashCode的作用】

  • hashCode方法的主要作用是为了配合基于散列的集合一起使用,比如HashSet、HashMap等等;
  • 当向集合中插入对象时,需要调用hashCode方法进行判别集合中是否已经存在该对象了(注意:集合中不允许元素重复);
  • 采用hashCode方法逐一比较比调用equals方法效率更高。

【hashCode与equals的关系】

  • 等价对象产生相同的哈希码,不同对象不一定要不同的哈希码;
  • 如果调用equals方法得到的结果为true,则lg

Error和Exception的区别,并列出5个运行时异常。

Error和Exception都继承自Throwable;

Exception Error
可以是可被控制的或不可控制的 总是不可控制的
表示一个由程序员导致的错误 用来表示系统错误或底层资源的错误
应该在应用程序级被处理 如果可能的话,应该在系统级被捕捉

运行时异常:

  1. ArrayIndexOutOfBoundsException数组下标越界
  2. StringIndexOutOfBoundsException字符串下标越界
  3. NullPointerException空指针异常
  4. ClassCastException类转换异常
  5. ClassNotFoundException无法找到指定的类异常
  6. IOException输入输出异常
  7. SQLException操作数据库异常

5. 讲讲类的加载,加载时机,类加载器,类的实例化顺序,双亲委派机制,如何打破。new一个对象的过程中发生了什么?

【类加载】当程序要使用某个类时,如果该类还没有被加载到内存中,则系统会通过加载、连接、初始化三步来对初始化类

  • 加载:通过一个类的全限定名获取此类的class文件,读入内存,并为之创建一个Class对象;
  • 连接:
  1. 验证:确保被加载类的正确性;
  2. 准备:负责为类的静态成员分配内存,并设置默认初始值,这些内存都在方法区中分配;
  3. 解析:将类中的符号引用替换为直接引用;
  • 初始化:局部变量保存在栈区,必须手动初始化;new的对象保存在堆区,虚拟机会进行默认初始化,基本数据类型初始化值为0,引用类型初始化值为null。

【类加载的时机】(只加载一次,以下时机表示第一次的时候)

  • 调用类中的静态属性或静态方法
  • 创建对象
  • 使用反射方式来强制创建某个类或接口对应的Class对象时,比如Class.forName("一个类的包名.类名")
  • 初始化某个类的子类的时候
  • 执行main方法时,main方法所在的类也会被加载

【类加载器】负责将.class文件加载到内存中,并为之生成对应的Class对象

  • 启动类加载器(Bootstrap ClassLoader),用来加载Java的核心类库,在JDK中JRE的lib目录下rt.jar文件中的类;
  • 扩展类加载器(Extension ClassLoader),用来加载Java的扩展库,在JDK中JRE的lib目录下ext目录;
  • 系统类加载器(SystemClassLoader),根据Java应用的类路径(CLASSPATH)来加载Java类(第三方类库),主要是我们开发者自己写的类。

【类的实例化顺序】

1. 当对象所在的类被加载时,

  1. 父类的静态属性、静态代码块会被执行(顺序是从上至下,根据定义的顺序)
  2. 子类的静态属性、静态代码块会被执行(顺序是从上至下,根据定义的顺序)

注:静态属性和静态代码块只会执行一次,换言之,静态属性和静态代码块在类加载时执行,而类只会加载一次

2. 对象所在的类被加载以后,当对象被创建时,

  1. 父类的成员属性、代码块会被执行(顺序是从上至下,根据定义的顺序)
  2. 父类的构造函数
  3. 子类的成员属性、代码块会被执行(顺序是从上至下,根据定义的顺序)
  4. 子类的构造函数

【双亲委派机制】

某个特定的类加载器接到类加载的请求时,首先将加载任务委托给父类加载器,依次递归,只有当父类加载器无法完成加载请求时,子类才会尝试自己去加载。

【如何打破双亲委派机制】

  • 自己写一个类加载器,继承ClassLoader类;
  • 重写loadClass方法,先尝试交由System类加载器加载,加载失败才会由自己加载;
  • 重写findClass方法。

【new一个对象的过程】

  • 先查看对象所在的类有没有被加载,若没有进行类加载(加载、连接、初始化);
  • 在堆区分配对象所需的内存(分配的内存包括本类和父类的所有实例变量,但不包括任何静态变量);
  • 对所有实例变量赋默认值(将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值);
  • 执行实例初始化代码(初始化顺序是先初始化父类再初始化子类,初始化时先执行实例代码块然后是构造方法);
  • 如果有类似于Child c = new Child()形式的c引用的话,在栈区定义Child类型引用变量c,然后将堆区对象的地址赋值给它。

6. 反射的原理,反射创建类实例的三种方式是什么。Class.forName和ClassLoader区别.

【反射原理】

Java程序可以加载一个在运行时才得知类名称的class,获悉其完整构造,并创建其对象,或对其任意属性设置,或调用其任意方法,这种动态获取的信息及动态调用对象的方法的功能称为Java语言的反射机制。

【class对象的三种创建方式】

  1. 类名.class;
  2. 对象.getClass();
  3. Class.forName("包名.类名");

【Class.forName和ClassLoader的区别】

  1. Java中Class.forName和ClassLoader都可以用来对类进行加载,
  2. 但是Class.forName除了将类加载到JVM中,还会对类进行解释,执行类中的static块,
  3. Class.forName(name, initialize, loader)带参函数,其中initialize参数可以控制是否加载static块。

7. 动态代理的几种实现方式,分别说出相应的优缺点。为什么CGlib方式可以对接口实现代理。

【链接】---- 动态代理

【动态代理】

动态代理是在运行期利用JVM的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入JAVA虚拟机执行。

【JDK动态代理】

  • 在java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口就可以生成代理类和代理实例
  • JDK动态代理机制是委托机制,具体说是动态实现接口类,在动态生成的实现类里面委托handler去调用原始实现类的方法
  • JDK动态代理只能对实现接口的类生成代理; 
  • 创建代理对象效率较高,执行效率较低。

【cglib动态代理】

  • cglib可以对任意类生成代理对象,它的原理是对目标对象进行继承代理;
  • 如果目标对象被final修饰,那么该类无法被cglib代理。
  • 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的区别

Java面试所需的知识_第14张图片

11. 深拷贝和浅拷贝区别

Java面试所需的知识_第15张图片

【clone方法】

  • 克隆一个对象
  • 是一种浅克隆,只克隆第一层
  • 调用克隆方法的两个要求:类实现Clonable接口,提升访问修饰符为public

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
  • Integer是int的包装类,int是基本的数据类型
  • Integer的默认值是null,int的默认值是0
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(Blocking I/O):同步阻塞I/O模式

       数据的读取写入必须阻塞在一个线程内等待其完成。这里使用那个经典的烧开水例子,这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。

  • NIO(New I/O):非阻塞I/O模式

【链接】---- 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中。

  • AIO(Asynchronous I/O):异步非阻塞I/O模式

       异步非阻塞无需一个线程去轮询所有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
	{
	//成员内部类
	}
}
  • 成员内部类可以直访问外部类的成员,包括私有的,也可以通过外部类名.this.成员名调用外部类成员;
  • 外部类访问非静态成员内部类的成员,必须要创建内部成员类的对象;
  • 外部类可以直接通过类名直接访问静态内部类的静态成员,包括私有的;
  • 成员内部类和外部类不存在继承关系;
  • 在其他类中生成成员内部类的方式:Outer.Inner oi = new Outer().new Inner()。

---------------------------------------------------------------------------------------------------------

(2)局部内部类:定义在外部类的方法里面

class Outer
{
	public void method()
	{
		final int a = 10;
		class Inner
		{
			System.out.println(a);
			//局部内部类
		}
	}
}
  • 局部内部类可以直接访问外部类的成员;
  • 在局部位置可以创建局部内部类的对象,通过对象调用成员;
  • 注意: 局部内部类在访问局部变量时,被访问的变量必须用 final 修饰,这是因为局部变量随着方法调用完毕就消失了,而内部类在堆内存中并不会立即消失,所以用 final 修饰,因为被 final 修饰的变量就成了常量,即使局部变量消失了,但数值并不会消失。

---------------------------------------------------------------------------------------------------------

【匿名内部类】没有名字的内部类,在一个类中获取一个接口的实现类对象或者获取一个抽象类的子类对象

(1)作用:正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写。

(2)匿名内部类的实现:第一种,继承一个类,重写其方法;第二种,实现一个接口(可以是多个),实现其方法。

(3)匿名内部类的注意事项

  • 匿名内部类不能有构造方法。

  • 匿名内部类不能定义任何静态成员、方法和类。

  • 匿名内部类不能是public,protected,private,static。

  • 只能创建匿名内部类的一个实例。

  • 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。

  • 因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

  • 匿名类和内部类中的中的this:有时候,我们会用到一些内部类和匿名类。当在匿名类中用this时,这个this则指的是匿名类或内部类本身。这时如果我们要使用外部类的方法和变量的话,则应该加上外部类的类名。

(4)匿名内部类的应用举例:比如多线程的实现上,实现多线程必须继承Thread类或是实现Runnable接口

  • Thread类的匿名内部类实现
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();
  }
}
  • Runnable接口的匿名内部类实现
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表达式和函数式接口

  • 目的:用于简化 匿名内部类的 定义和创建,函数作为参数传递进方法中
  • 使用场景:只能用于函数式接口(只有一个抽象方法的接口)
  • 四种接口:Consumer,Supplier,Predicate,Function

(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异常.
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型
【参数化类型】

  • 把类型当作是参数一样传递
  • <数据类型>只能是引用类型

【作用】

  • 代码更加简洁【不用强制转换】
  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
  • 可读性和稳定性【在编写集合的时候,就限定了类型】

19. 简单描述正则表达式及其用途。

【java.util.regex】

  • 在regex包中,包括了两个类——Pattern(模式类)和Matcher(匹配类);
  • Pattern类是用来表达和陈述所要搜索模式的对象;Pattern 类没有公共构造方法。要创建一个 Pattern 对象,你必须首先调用其公共静态编译方法,它返回一个 Pattern 对象。
  • Matcher类是对输入字符串进行解释和匹配操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。
  • regex包中还有一个非强制异常类PatternSyntaxException,它表示一个正则表达式模式中的语法错误。

【多个匹配器可以共享同一模式】

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

  1. 修饰类时,表明这个类不能被继承;
  2. 修饰方法时,方法不能被重写;
  3. 修饰变量时,final成员变量表示常量,只能被赋值一次,赋值后值不再改变:
  • 当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
  • 如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
  • 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语句


2. JVM

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】:

  • 程序的顺序性规则:一个线程中,前面的操作happens-before于后续的任意操作
  • volatile变量规则:一个volatile变量的写操作,happens-before于后续对这个volatile变量的读操作
  • 传递性:如果A happens-before B,而B happens-before C,那么A happens-before C
  • 管程中锁的规则:对一个锁的解锁,happens-before于随后对这个锁的加锁操作
  • 线程start()规则:如果线程A执行ThreadB.start()操作,那么线程A的ThreadB.start()操作happens-before 于线程B中的操作
  • 线程join()规则:如果线程A执行ThreadB.join()操作,那么线程B中的任意操作happens-before 于 线程A从ThreadB.join()操作成功返回。

【主内存和工作内存】:所有线程共享主内存,每个线程都有自己的工作内存,其中工作内存是cpu的寄存器和高速缓存的抽象描述。


4.  如何检测垃圾——引用计数法和可达性分析法

引用计数法:很难解决对象之间相互循环引用的问题

根对象集合(GC Roots)和一个对象之间有没有可达路径,GC Root的对象包括以下几种:

  • 虚拟机栈中引用的对象
  • 本地方法栈中Native方法引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

5. 基于分代的垃圾收集算法

【链接】---- Java 垃圾回收机制

Java面试所需的知识_第16张图片

新生代:分为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触发的。

Java面试所需的知识_第17张图片


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收集器的最大特点】

  •  G1最大的特点是引入分区的思路,弱化了分代的概念。
  •  合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。

【2.G1相比较CMS的改进】

  •  算法: G1基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次FULL GC。
  •  停顿时间可控: G1可以通过设置预期停顿时间(Pause Time)来控制垃圾收集时间避免应用雪崩现象。
  •  并行与并发:G1能更充分的利用CPU,多核环境下的硬件优势来缩短stop the world的停顿时间。

【3.CMS和G1的区别】

  •  CMS中,堆被分为PermGen,YoungGen,OldGen;而YoungGen又分了两个survivo区域。在G1中,堆被平均分成几个区域(region),在每个区域中,虽然也保留了新老代的概念,但是收集器是以整个区域为单位收集的。
  •  G1在回收内存后会马上同时做合并空闲内存的工作、而CMS默认是在STW(stop the world)的时候做。
  •  G1会在Young GC中使用、而CMS只能在O区使用。

【4.G1收集器的应用场景】

G1垃圾收集算法主要应用在多CPU大内存的服务中,在满足高吞吐量的同时,尽可能的满足垃圾回收时的暂停时间。

就目前而言、CMS还是默认首选的GC策略、可能在以下场景下G1更适合:

  •  服务端多核CPU、JVM内存占用较大的应用(至少大于4G)
  •  应用在运行过程中会产生大量内存碎片、需要经常压缩空间
  •  想要更可控、可预期的GC停顿周期,防止高并发下应用雪崩现象

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.热点分析

  • CPU热点:检查哪些方法占用大量的CPU时间
  • 内存热点:检查哪些对象在系统中数量最大

3. Java并发编程

1. 并发编程的几个挑战

【上下文切换的问题】

【死锁的问题】

【硬件和软件资源限制的问题】

2. 线程间通信

线程间的通信机制有两种:共享内存和消息传递

而Java线程之间的通信总是隐式进行的,采用的是共享内存模型,Java线程间的通信由Java内存模型(JMM)控制。

Java面试所需的知识_第18张图片


1、互斥(无锁和互斥锁)

1. synchronized的原理是什么,解释以下名词:自旋锁,偏向锁,轻量级锁,可重入锁,公平锁,非公平锁,乐观锁,悲观锁。

【synchronized锁的三种表现形式】

  • 对于普通成员方法,锁是当前实例对象;
  • 对于静态成员方法,锁是当前类的class对象;
  • 对于同部方法块,锁是synchronized括号里配置的对象。

【Synchronized实现原理】

  • 基于monitorenter和monitorexit指令实现,在编译后分别插入到同部代码块的开始位置和方法结束处(或异常处)。
  • synchronized用的锁是存在Java对象头里的,对象头里的Mark Word默认存储对象的Hash Code、分代年龄和锁标记位。
  • 锁一共有4种状态,从低到高是:无锁状态、偏向锁状态、轻量级锁状态和重量级状态,锁可以升级但是不能降级,这种策略是为了提高获得锁和释放锁的效率。

【偏向锁】

偏向锁使用了一种等到竞争出现才释放锁的机制

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)吗,原理是什么,一般在什么场景下用。

  • 同步是
  • 基于AQS实现的,在AQS中核心是state字段和双端队列;
  • 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

  • 往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器;
  • 可以并发的读,而不需要加锁;
  • 向CopyOnWriteArrayList中add方法,在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来;
  • CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景。

9. 用过哪些原子类,他们的原理是什么。

AtomicInteger、AtomicLong、AtomicBoolean、AtomicIntegerArray、AtomicLongArray.....

【一些具体的操作方法】

通过 AtomicInteger()或AtomicInteger(int num) 初始化:

               getAndIncrement():原子递增。

               getAndDecrement():原子递减。

               getAndAdd(int num):原子性地将给定值添加到当前值.

主要在极高并发下使用,使用时不需要手动同步,Atomic包下的原子类都实现了CAS操作

10. 不变模式

【定义】一个对象的状态在对象被创建之后就不再变化,这就是所谓的不变模式。

【弱不变模式】一个类的实例状态是不可变的,但是这个类的子类的实例状态可能会变化。

【强不变模式】一个类的实例状态不会改变,它的子类的实例状态也不会改变。

  1. 去除所有setter方法以及可以修改自身属性的方法;
  2. 将所有属性设置为private的,并用final标记,确保其不可修改;
  3. 确保没有子类可以继承该类;
  4. 有一个可以创建完整对象的构造函数。

【使用场景】

  1. 当对象创建后,其内部状态和数据不再发生任何改变;
  2. 对象需求被共享、被多线程频繁访问。
  3. java.lang.String类

2、线程

1. 多线程的几种实现方式,什么是线程安全的

  • 继承Thread类,重写Thread类的run方法
  • 实现Runnable接口
  • 实现Callable接口,实现call方法,增加了异常和返回值
  • 基于线程池的方式

【链接】---- java多线程的实现方式


3. 用过线程池吗,如果用过,请说明原理,并说说newCache和newFixed有什么区别,构造函数的各个参数的含义是什么,比如coreSize,maxsize等。

【链接】---- 深入分析java线程池的实现原理

【链接】---- Java线程池的分析和使用

【使用场景】单个任务处理时间比较短,需要处理的任务数量较多

【好处】降低资源消耗,提高响应速度,提高线程的可管理性

【概述】Executor框架把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行、被哪个线程执行,以及什么时候执行

Java面试所需的知识_第19张图片

【ThreadPoolExecutor的构造函数的参数含义】

  • corePoolSize:线程池中的核心线程数
  • maximumPoolSize:线程池中允许的最大线程数
  • keepAliveTime:线程空闲时的存活时间
  • unit:keepAliveTime的单位
  • workQueue:用来保存等待被执行的任务的阻塞队列,且任务必须实现Runnable接口
  • RejectedExecutionHandler:饱和策略

【提交任务的两种方法】

  • 使用execute方法,execute方法输入的任务是一个Runnable类的实例,所以没有返回值,所以无法判断任务知否被线程池执行成功;
  • 使用submit方法,它会返回一个future,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成。

【newFixedThreadPool和newCacheThreadPool的区别】见下表

newFixedThreadPool newCacheThreadPool
初始化指定一个线程数的线程池 初始化一个可以缓存线程的线程池
corePoolSize == maximumPoolSize,在初始化的时候指定 coreSize=0,maxSize=Integer.MAX_VALUE
LinkedBlockingQueue作为阻塞队列 SynchronousQueue作为阻塞队列
当线程池没有可执行的任务时,也不会释放线程 当线程的空闲时间超过KeepAliveTime时,会自动释放线程资源

【阻塞队列】

  1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  2. LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
  3. SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  4. PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

【合理的配置线程池】首先分析任务的特性,从以下几个角度分析:

  1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
  2. 任务的优先级:高,中和低。
  3. 任务的执行时间:长,中和短。
  4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

【饱和策略RejectedExecutionHandler】

  1. AbortPolicy:默认情况下执行此策略,表示无法处理新任务时直接抛出异常;
  2. CallerRunsPolicy:只用调用者所在线程来运行任务。
  3. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  4. DiscardPolicy:不处理,丢弃掉。
  5. 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

【线程池的监控】

  • 通过线程池提供的参数进行监控
  1. taskCount:线程池需要执行的任务数量。
  2. completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
  3. largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
  4. getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
  5. getActiveCount:获取活动的线程数。
  • 通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法进行监控
  1. 可以在任务执行前,执行后和线程池关闭前干一些事情。
  2. 如监控任务的平均执行时间,最大执行时间和最小执行时间等。
  3. 这几个方法在线程池里是空方法。

4. 线程池的关闭方式有几种,各自的区别是什么。

(1)shutdown关闭线程池

  • 线程池的状态变为SHUTDOWN状态,此时不能再往线程池中添加新的任务,否则会抛出RejectedExecutionException异常
  • 线程池不会立即退出,直到添加到阻塞队列中的任务都处理完成

(2)shutdownNow关闭线程池并中断任务

  • 线程池的状态变为STOP状态
  • 试图停止当前正在执行的任务,并返回尚未执行的任务列表

6. 导致线程死锁的原因?怎么检测死锁?怎么预防线程死锁。

【链接】----- 死锁终极篇

【死锁定义】一组互相竞争资源的线程相互等待,导致“永久”阻塞的现象

【死锁的条件】

  • 互斥,共享资源只能被一个线程使用
  • 占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X
  • 不可抢占,其他线程不能强行抢占线程T1的资源
  •  循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程T1的资源,这就是循环等待

【死锁检测工具】

1、Jstack工具—— jstack是JVM自带的一种堆栈跟踪工具

2、JConsole工具 —— JConsole是JDK自带的监控工具

【规避死锁】并发程序一旦发生死锁,一般只能重启应用,因此,解决死锁的最好办法还是规避死锁。只要破坏上面其中一个死锁的条件,就可以成功避免死锁的发生。

  • 互斥这个条件没有办法破坏,因为用锁就是为了互斥;
  • 破坏“占有且等待”这个条件;
  1. 添加一个管理员类,类中有同时申请资源和同时释放资源两个方法,向管理员一次性申请所有资源,这样就不会出现占有且等待的现象了;
  2. 避免一个线程同时获取多个锁;
  3. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
  • 破坏“不可抢占”这个条件,核心是能够主动释放占有的资源,采用JUC包下的Lock,使用定时锁boolean tryLock(long time, TimeUnit unit)方法按照固定时长等待加锁,超时未获得锁就会主动释放之前已经获得的所有的锁,然后等待一段随机的时间再重试。
  • 破坏“循环等待”这个条件,顺序访问资源。为每个资源设一个ID号,所有线程按照ID从小到大的顺序访问资源
  • 死锁检测
/**********破坏“占有且等待”这个死锁条件***************/
class Allocator {
  private List als =
    new ArrayList<>();
  // 一次性申请所有资源
  synchronized boolean apply(
    Object from, Object to){
    if(als.contains(from) ||
         als.contains(to)){
      return false;  
    } else {
      als.add(from);
      als.add(to);  
    }
    return true;
  }
  // 归还资源
  synchronized void free(
    Object from, Object to){
    als.remove(from);
    als.remove(to);
  }
}

class Account {
  // actr 应该为单例
  private Allocator actr;
  private int balance;
  // 转账
  void transfer(Account target, int amt){
    // 一次性申请转出账户和转入账户,直到成功
    while(actr.apply(this, target))
      ;
    try{
      // 锁定转出账户
      synchronized(this){              
        // 锁定转入账户
        synchronized(target){           
          if (this.balance > amt){
            this.balance -= amt;
            target.balance += amt;
          }
        }
      }
    } finally {
      actr.free(this, target)
    }
  } 
}
 
  
/**********破坏“循环等待”这个死锁条件***************/
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. 画一个线程的生命周期状态图。

Java面试所需的知识_第20张图片

8. sleep和wait的区别。

wait sleep
调用对象的wait方法,必须先获取对象锁 静态方法,不需要获取锁
进入到等待池 进入到阻塞状态
需要其他线程唤醒 到时自己醒
会释放锁资源 不会释放锁资源

9. sleep和sleep(0)的区别。

在线程中,调用sleep(0)可以释放cpu时间,让线程马上重新回到就绪队列而非阻塞队列,sleep(0)释放当前线程所剩余的时间片(如果有剩余的话),这样可以让操作系统切换其他线程来使用,提升效率。

10. 如何减少上下文切换

【背景】

  • 时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒;
  • 当前任务执行一个时间片以后会切换到下一个任务,任务的切换就是任务从保存到再加载的过程,即一次上下文切换。

【方法】

  • 无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同线程处理不同段的数据。
  • CAS算法:Java的Atomic包使用的CAS算法来更新数据,而不需要加锁。
  • 使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,会造成大量线程处于等待状态。
  • 使用协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

3、协作

1. 信号量Semaphore,计数信号量

【链接】---- Java并发编程之Semaphore

  • 信号量维护了一个信号量许可集;
  • 线程可以通过调用acquire()来获取信号量资源;如果当前信号量计数个数大于 0 ,并且当前线程获取到了一个信号量则该方法直接返回,当前信号量的计数会减少 1 。否则会被放入AQS的阻塞队列,当前线程被挂起,直到其他线程调用了release方法释放了信号量,并且当前线程通过竞争获取到了改信号量。
  • 线程可以通过release()来释放它所持有的信号量资源;
  • Semaphoren内部是使用AQS来实现的,构造函数里面传递的初始化信号量个数 permits 被赋值给了AQS 的state状态变量,也就是说这里AQS的state值表示当前持有的信号量个数。

2. 管程Monitor

 

3. CountDownLatch

countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别(比如countdownlatch的await方法和是怎么实现的)。

4、分工

1. 生产者消费者模型

【实现方法】

  • Object的wait()/notify()方法
  • Lock和Condition的await()/

5. 开启多个线程,如果保证顺序执行,有哪几种实现方式,或者如何保证多个线程都执行完再拿到结果。

用三个线程按顺序循环打印abc三个字母,比如abcabcabc。

【链接】---- 多个线程顺序执行的几种方法

【链接】---- 

(1)使用信号量Semaphore

  • 需要先构建一个参数来指定共享资源的数量,Sephmore s = new Sephmore(1,true);//一个共享资源,true表示FIFO
  • 获取Sephmore,s.acquire();
  • 共享资源使用完毕后释放资源,s.release();

(2)使用ReentrantLock

(3)join方法

(4)newSingleThreadExecutor只有一个线程的线程池

(5)newCachedThreadPool

(6)使用CountDownLatch

Java面试所需的知识_第21张图片

假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有10个线程同时调用它,如何做到。

spring的controller是单例还是多例,怎么保证并发的安全。

如果让你实现一个并发安全的链表,你会怎么做。

 

讲讲java同步机制的wait和notify。

 

多线程如果线程挂住了怎么办。

 

对AbstractQueuedSynchronizer了解多少,讲讲加锁和解锁的流程,独占锁和公平所加锁有什么不同。

 

简述ConcurrentLinkedQueue和LinkedBlockingQueue的用处和不同之处。

 

延迟队列的实现方式,delayQueue和时间轮算法的异同。


4. Java集合框架

1. 说说你知道的几个线程安全的Java集合类:list、set、queue、map实现类。

Java面试所需的知识_第22张图片

【List】

CopyOnWriteArrayList:线程安全的List,在读多写少的场合性能非常好,远好于Vector,Vector和Stack也是线程安全的;

【Set】

ConcurrentArraySet:线程安全的Set;

【Queue】

ConcurrentLinkedQueue:高效的并发队列,使用链表实现。可以看做线程安全的 LinkedList,这是一个非阻塞队列。

【Map】

ConcurrentHashMap:线程安全的HashMap,HashTable也是线程安全的。


2. fail-fast机制

【定义】

  • fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。
  • 在迭代的过程中该集合在结构上发生改变的时候,就有可能发生fail-fast,即抛出ConcurrentModificationException异常
  • fail-fast机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测bug。

【出现场景】

ArrayList,HashMap在单线程和多线程环境下都有可能出现

迭代器遍历时,在某一步迭代中remove一个元素;

一个线程迭代,另一个线程remove;

if (modCount != expectedModCount)   throw new ConcurrentModificationException();

【避免fail-fast】

  • 迭代器遍历时,调用迭代器的remove方法而不是集合类的remove方法;
  • 采用CopyOnWriteArrayList代替ArrayList,ConcurrentHashMap代替HashMap;
  • CopyOnWriter是写时复制的容器,在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。
  • ConcurrentHashMap当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。

3. ArrayList和LinkedList有什么区别。

【ArrayList】

  • 底层是基于动态数组;
  • 根据下标访问数组元素,因为数组是连续内存,会有一部分或全部数据一起加载到CPU缓存,所以查询快;
  • 插入、删除效率低,尤其头部插入、头部删除O(n),不适合动态存储,动态添加;
  • JDK1.8  数组扩容后的容量是扩容前的1.5倍

【LinkedList】

  • 底层是基于双向链表,不需要连续空间,长度不固定;
  • 元素访问速度慢O(n);
  • 插入、删除操作快O(1)。

4. HashMap的源码,实现原理,底层结构。

  • HashMap的主干是一个Entry数组
  • transient Entry[] table = (Entry[]) EMPTY_TABLE; //主干数组的长度一定是2的次幂,设数组长度为M
  • HashMap的散列函数是hashCode()方法,这个方法会返回一个32位的整数,不能直接作为数组的索引
  • 使用hash方法增加hashCode的“随机度”,提高离散性能,以减少哈希冲突,将hashCode的高16位与低16位进行异或运算
  • 最后将hash值计算出一个在0~M-1范围内的数,使用除留余数法,hash % M
  • 使用位运算代替取模运算,效率更高,即N%M等价于N&(M-1),M为2的次方;
  • 所以(h = key.hashCode()) ^ (h >> 16) & (M-1)
  • HashMap采用链地址法(数组+链表)解决哈希冲突的问题

【put函数大致的思路为】:

  • 对key的hashCode()做hash,然后再计算index;
  • 如果没碰撞直接放到Entry数组里;
  • 如果碰撞了,以链表的形式存在当前entry的后面;
  • 如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树(若结点个数减少到6时候,则将红黑树转化回链表);
  • 如果节点已经存在就替换old value(保证key的唯一性)
  • 如果数组满了(超过load factor*current capacity),就要扩容resize。

【get函数大致的思路为】:

  • Entry数组里的第一个节点,直接命中;
  • 如果有冲突,则通过key.equals(k)去查找对应的entry
  • 若为树,则在树中通过key.equals(k)查找,O(logn);
  • 若为链表,则在链表中通过key.equals(k)查找,O(n)。

【扩容是以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. 降低了锁粒度,去掉了segment数组,在锁住Node之前的操作是基于在volatile和CAS之上实现无锁,后面锁住的是HashEntry首节点
  2. 在java8中,当链表内的数量满足一定条件时,链表会变成红黑树,加快搜索速度。
  3. 使用volatile修饰Node元素的val和指针next,保证了在多线程时的可见性,使得get读操作不需要加锁
  4. 使用volatile修饰table数组,使得在扩容的时候对其他线程具有可见性

(1)几个重要的知识点

  1. 默认数组大小为16,扩容因子为0.75,扩容后数组大小翻倍;
  2. 当存储的node总数量>= 数组长度*扩容因子时,会进行扩容(数组中的元素、链表元素、红黑树元素都是内部类Node的实例或子类实例,这里的node总数量是指所有put进map的node数量);
  3. 当数组下是链表时,在扩容的时候从链表的尾部开始rehash;
  4. 当一个事务操作发现map正在扩容时,会帮助扩容;
  5. map正在扩容时获取(get等操作)操作还没进行扩容的下标会从原来的table获取,扩容完的下标会从新的table中获取。

 (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)几个主要方法的实现原理

  • 【put方法】
  1. 首先检查table有没有初始化,没有则初始化table,lazy初始化;
  2. 定位到下标,如果这个位置没有值,直接放进去,使用CAS保证线程安全,不需要加锁
  3. 定位到下标后如果下标内是代表rehash的特殊node,则会帮助扩容;
  4. 如果2,3都不是,就在下标对应的头结点使用Synchronized加锁,锁住头结点;
  5. 在链表末尾添加node
  6. 如果链表数量>=8则会变成红黑树;
  7. 在最后增加map的总node数量时,若总数超过table长度的0.75倍则会进行扩容,扩容时会将下标倒序分为几块任务,可由其余线程帮助完成。
  • 【get方法】get时会根据key定位到下标,然后遍历链表或红黑树查找对应的node,如果下标内是代表rehash的特殊node,则会去临时扩容table内查询数据;使用volatile修饰Node元素的val和指针next,保证了在多线程时的可见性,使得get读操作不需要加锁。
  • 【size方法】JDK 8新增了mappingCount 方法,其返回值是 long 类型。

    在没有并发的情况下,使用一个 baseCount volatile 变量就足够了,当并发的时候,CAS 修改 baseCount 失败后,就会使用 CounterCell 类了,会创建一个这个对象,通常对象的 volatile value 属性是 1。在计算 size 的时候,会将 baseCount 和 CounterCell 数组中的元素的 value 累加,得到总的大小,但这个数字仍旧可能是不准确的。

    还有一个需要注意的地方就是,这个 CounterCell 类使用了 @sun.misc.Contended 注解标识,这个注解是防止伪共享的。是 1.8 新增的。使用时,需要加上 -XX:-RestrictContended 参数。

  • 【remove方法】remove时会根据下标遍历下标内的链表或红黑树,如果下标内是代表rehash的特殊node,则会帮组扩容

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实现类,他们是怎么保证有序的。

  • TreeMap根据key进行排序
  • LinkedHashMap则记录了插入顺序

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实现原理。

  • TreeSet是一个有序的、没有重复元素的集合,自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序;
  • TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的;
  • 基于TreeMap实现的;
  • 是非线程安全。

10. Java中的HashSet内部是如何工作的。

HashSet底层是基于HashMap来存储的,遍历table中的元素实现存储不重复元素:

如果hash值不同,说明是一个新元素,直接存储;

如果hash值相同,且equals方法判断相等,说明元素已经存在,不存;

如果hash值相同,且equals方法判断不相等,说明元素不存在,存。

13. 栈和队列的区别,各自的应用场景

【栈】先进后出

【链接】---- 栈的应用举例

  1. 将十进制正整数num转换为n进制
  2. 检验符号是否匹配
  3. 行编辑:输入行中字符'#'表示退格, '@'表示之前的输入全都无效
  4. Java栈中存放的是一个个栈帧,每个栈帧对应的是一个被调用的方法,线程当前执行的方法一定是位于Java栈的栈顶的

【队列】先进先出

【链接】----  消息队列应用场景

消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋,日志处理等问题;

使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ(淘宝),RocketMQ

应用解耦

Java面试所需的知识_第23张图片

在下单时库存系统不能正常使用。也不影响正常下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了。实现订单系统与库存系统的应用解耦

异步消息

Java面试所需的知识_第24张图片

流量削锋

Java面试所需的知识_第25张图片

应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

  • 可以控制活动的人数

  • 可以缓解短时间内高流量压垮应用

  • 用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面

  • 秒杀业务根据消息队列中的请求信息,再做后续处理

日志处理

日志处理是指将消息队列用在日志处理中,比如Kafka的应用,解决大量日志传输的问题。架构简化如下

 Java面试所需的知识_第26张图片

  • 日志采集客户端,负责日志数据采集,定时写受写入Kafka队列

  • Kafka消息队列,负责日志数据的接收,存储和转发

  • 日志处理应用:订阅并消费kafka队列中的日志数据


14. Java中的队列都有哪些,有什么区别。

实现Queue的类 特点 数据结构 线程安全
PriorityQueue,优先级队列 保持队列的顺序是按照元素的大小重新排序的 数据结构是堆,底层是用数组保存数据 不安全
Deque,双端队列 既可以当成双端队列使用,也可以被当成栈来使用    

ArrayDeque循环队列

用数组实现的Deque   不安全

【ArrayBlockingQueue数组实现的线程安全的有界的阻塞队列】

【链接】---- Java并发包--ArrayBlockingQueue

  • 内部是通过Object[]数组保存数据的,即本质上是通过数组实现的,ArrayBlockingQueue的大小是创建时指定的;
  • ArrayBlockingQueue是根据ReentrantLock该互斥锁实现“多线程对竞争资源的互斥访问”,默认会使用非公平锁;
  • ArrayBlockingQueue与Condition是组合关系,ArrayBlockingQueue中包含两个Condition对象(notEmpty和notFull);
  1. 若某线程(线程A)要取数据时,数组正好为空,则该线程会执行notEmpty.await()进行等待;当其它某个线程(线程B)向数组中插入了数据之后,会调用notEmpty.signal()唤醒“notEmpty上的等待线程”。此时,线程A会被唤醒从而得以继续运行。
  2. 若某线程(线程H)要插入数据时,数组已满,则该线程会它执行notFull.await()进行等待;当其它某个线程(线程I)取出数据之后,会调用notFull.signal()唤醒“notFull上的等待线程”。此时,线程H就会被唤醒从而得以继续运行。

5. Servlet

1. 什么是Servlet?

Servlet是用来处理客户端请求并产生动态网页内容的Java类。在无状态的HTTP协议下管理状态信息。

2. Servlet运行过程。

  1. 浏览器发送请求到服务器,到达服务器之后会将请求中的数据打包,封装成一个请求对象HttpServletRequest
  2. 同时还会创建一个响应对象HttpServletResponse
  3. 服务器根据请求查看web.xml文件,找到对应的Servlet
  4. 调用其中的service方法(doPost/doGet),将请求对象HttpServletRequest和响应对象HttpServletResponse传递给方法
  5. service方法根据请求完成核心的业务逻辑,并提供响应的视图
  6. 服务器根据响应对象生成响应结果发送给客户端
  7. 浏览器渲染展现页面

3. Servlet的生命周期,是否有线程安全问题,如何解决?

【生命周期】

  1. Servlet容器加载Servlet类;
  2. 通过反射机制创建Servlet实例;
  3. Servlet容器调用init()方法完成初始化工作,单线程,只初始化一次;
  4. 调用service方法对请求进行处理,运行在多线程,注意线程安全;
  5. 调用实例的destroy方法以便让其释放他使用的资源。

【是否有线程安全问题】

一个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");

【请求乱码】

  • post请求
//告诉服务器,以指定的编码解析请求的数据
request.setCharacterEncoding("指定编码");
  • get请求

方案一:解码,将ISO8859-1的编码转换成指定的编码

name = new String(name.getBytes("ISO8859-1"),"UTF-8");

方案二:修改tomcat默认字符集

【jdbc乱码】?useUnicode=true&characterEncoding=utf-8

【数据库乱码】

建表时指定搜索引擎以及字符集:engine = Innodb default charset=utf8;

【页面乱码】

6. 页面跳转——转发与重定向

【重定向】

  • 表示重新发送一个请求
  • 该请求指向了另一个地址
  • 重定向的状态码为:302
  • response.sendRedirect(地址)

【转发】

  • 服务器内部进行地址的转换
  • request.getRequestDispatcher(地址).forward(request,response)
重定向 转发
地址栏发生了变化 地址栏没有发生改变
重新发送了一次请求 没有发生新的请求,服务器内部控制权的转移
没有共享请求与响应 共享请求与响应
无法访问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如何工作?

  • 编写一个Java类
  1. 该类实现Filter接口,并重写doFilter,选择性重写init、destroy方法;
  2. doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain):

          在Servlet的service方法调用之前调用,FilterChain代表当前的整个请求链,所以通过调用FilterChain.doFilter可以将请求            继续传递下去。

  • 配置过滤器

三、设计模式

1. 单例模式

  • java.lang.Runtime#getRuntime()

1、懒汉式,线程不安全,lazy初始化

Java面试所需的知识_第27张图片

2、懒汉式,线程安全,lazy初始化,效率很低

Java面试所需的知识_第28张图片

3、饿汉式,线程安全,不是lazy初始化,效率高但是浪费内存

Java面试所需的知识_第29张图片

4、双检锁方式,线程安全,lazy初始化

Java面试所需的知识_第30张图片

5、静态内部类,线程安全,lazy初始化

Java面试所需的知识_第31张图片

6、枚举式,线程安全,不是lazy初始化,防止利用反射强制重复构建对象,可以反序列化创建对象

Java面试所需的知识_第32张图片

2. 工厂模式—— 创建对象的创建型模式

  • java.lang.Proxy#newProxyInstance()

3. 适配器模式 —— 两个不兼容的接口之间的桥梁,结构型模式

【应用】

InputStreamReader和OutputStreamWriter,是从字节到字符的转换桥梁

4. 装饰器模式 —— 赋予现有的类更多的功能,结构型模式

【应用】

BufferedInputStream是具体的装饰器实现者,它给InputStream类附加了功能

5. 观察者模式 —— 行为型模式

【应用】

  • Tomcat的启动逻辑是基于观察者模式设计的
  • ZooKeeper是一个基于观察者模式设计的分布式服务框架

桥接模式也是拥有双方,同样是使用接口(抽象类)的方式进行解耦,使双方能够无限扩展而互不影响,其实二者还是有者明显的区别:

  1. 主要就是使用场景不同,桥接模式主要用于实现抽象与实现的解耦,主要目的也正是如此,为了双方的自由扩展而进行解耦,这是一种多对多的场景。观察者模式侧重于另一方面的解耦,侧重于监听方面,侧重于一对多的情况,侧重于一方发生情况,多方能获得这个情况的场景。
  2. 另一方面就是编码方面的不同,在观察者模式中存在许多独有的内容,如观察者集合的操作,通知的发送与接收,而在桥接模式中只是简单的接口引用。

6. 责任链模式 —— 行为型模式

【应用】

Filter是基于责任链模式设计的

7. 桥接模式

把抽象和实现解藕,于是接口和实现可在完全独立开来。

JDBC

8. 建造者模式

StringBuilder.append

Guava缓存创建对象

9. 外观模式

slf4j日志接口

你可能感兴趣的:(Java)