Java面试整理
Java
基础
接口和抽象类的区别
接口(一种规范) | 抽象类(一种模板样式设计) | |
---|---|---|
方法 | public abstract方法,不能为普通方法提高方法实现 | 都可以,抽象方法必须为public或者protected |
变量 | public static final变量,不能定义普通成员变量 | 都可以 |
构造器 | 不包含构造器 | 可以有 |
初始化块 | 不包含 | 可以有 |
集合
红黑树
特性:
节点是红色或黑色。
根节点是黑色。
每个叶子节点都是黑色的空节点(NIL节点)。
每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
关键特性:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。
I/O
多线程
jdk1.6之后synchronized锁升级过程
无锁(01):对象头开辟25bit存储hashcode
偏向锁(01):线程id
轻量级锁(00):指向栈中锁记录的指针
重量级锁(11):指向互斥量(重量级锁)的指针
线程池7个主要参数:
- corePoolSize(核心工作线程数)线程池核心线程数最大值
- maximumPoolSize(最大线程数) 线程池最大线程数大小
- keepAliveTime(多余线程存活时间)线程池中非核心线程空闲的存活时间大小
- unit(时间单位)线程空闲存活时间单位
- workQueue(队列)存放任务的阻塞队列
- threadFactory(线程创建工厂)用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
- handler(拒绝策略)线城池的饱和策略事件,主要有四种类型。
四种拒绝策略
AbortPolicy:丢弃任务并抛出RejectedExectionException异常
DiscardPolicy:丢弃任务,但是不抛出异常
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
CallerRunsPolicy:由调用线程处理该任务
生产者消费者
JMM
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范.
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
JMM的实现
原子性:synchronized,monitorenter和monitorexit指令
可见性:volatile,synchronized,final
有序性:volatile,synchronized
JVM
JVM启动过程
JVM的装入环境和配置
装载JVM
初始化JVM
运行Java程序
类加载的过程
七个阶段:加载、验证、准备、解析、初始化、使用、卸载
双亲委派模型
启动类加载器 -> 扩展类加载器 -> 应用程序类加载器 -> 自定义类加载器。
垃圾回收算法
标记-清除算法:先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象。(缺点:1.执行效率不稳定;2.产生大量不连续的内存碎片)
复制算法:将内存划分为大小相等的两块,将还存活着的对象复制到另一块上面。
标记-整理算法:先标记出所有存活的对象,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
G1垃圾收集器
标记-整理、复制算法
新时代、老年代
并行与并发,高吞吐量,低停顿
面向服务端应用,jdk9之后成为默认垃圾收集器
CMS垃圾收集器
基于标记-清除算法实现
老年代
低停顿
数据库
MySQL
三大范式
第一范式:每个列都不可以再拆分。
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
索引
一种排好序的快速查找数据结构
MyISAM引擎和InnoDB引擎
MyISAM:索引文件和数据文件是分离的,索引文件仅保存数据记录的地址;data域存储的是数据记录的地址。
InnoDB:数据文件本身就是索引文件;辅助索引data域存储相应记录主键的值。
B+树
平衡查找树,所有记录节点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过指针进行链接。
特点:高扇出性
优点:支持范围查询,查询效率稳定。
聚簇索引和非聚簇索引
聚簇索引:根据主键创建的一棵B+树,叶子节点存放了表中的所有记录
非聚簇索引:根据索引键创建的一棵B+树,叶子节点仅存放索引键值,以及该索引键值指向的主键。
回表查询
通过普通索引查询到该索引键值指向的主键,再将拿到的主键值到聚簇索引中查询
覆盖索引
含义:需要查询的字段在当前索引对应的字段中都包含了,就不用回表。
实现方式:将被查询的字段,建立到联合索引里去,防止回表。
最左前缀原则
abc:a、ab、abc
索引失效
!=
<>
or 前后有一个以上字段不是索引
like %__
in 字符串不加单引号
违反最左前缀原则
事务
ACID
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
MVCC
读不加锁
隐藏列:事务id、undo log指针
基于undo log的版本链:指向更早版本的undo log
ReadView:快照,指定版本
四种隔离级别
读未提交(read uncommitted):允许读取未提交数据,存在脏读
读提交(read committed):允许读取提交数据,不存在脏读
可重复读(repeatable read):InnoDB使用next-key Lock算法实现了行锁,不允许读取已提交的数据,包含间隙锁,解决了幻读的问题
串行化(serializable):读占用了锁
优化
Redis
设置过期时间 :expire [key] [time]
查看到期时间:ttl [key]
五种基本数据类型及应用场景
String:数据缓存,热点数据,图片,视频
list:评论列表,实现队列或栈
hash:同string
set
zset:热点数据排行榜
缓存
缓存击穿:一个热点数据突然失效,大量请求直接到达数据库
缓存穿透:查询一条缓存和数据库都不存在的数据,每次请求都到达数据库
缓存雪崩:在某个时段,缓存集中失效,大量请求直接请求数据库
持久化存储
MongoDB
分片
框架
Spring
IoC
面向对象的设计原则,解决对象管理和对象依赖的问题,通过IOC容器管理创建获取对象和依赖关系,把对象进行统一管理。
AOP
面向切面编程思想,抽取公共的非业务代码,如:日志记录
Bean的生命周期
1、实例化Bean对象
2、设置Bean属性
3、如果通过各种Aware接口声明了依赖关系,则会注入Bean对容器基础设施层面的依赖。 Aware接口集体包括BeanNameAware、BeanFactoryAware和ApplicationContextAware 分别注入Bean ID、Bean Factory 和ApplicationContext
4、如果实现了BeanPostProcesser,调用BeanPostProcesser的前置初始化方法postProcessBeforeInitialization
5、如果实现了InitializingBean接口,则会调用afterPropertiesSet方法
6、调用Bean自身定义的init方法
7、调用BeanPostProcesser的后置方法postProcessAfterInitialization
创建完毕 使用 销毁
8、容器关闭前调用DisposableBean的destroy方法和自身的destroy方法
Spring MVC
Spring MVC的执行流程
- 整个过程开始于客户端发出的一个HTTP请求,Web应用服务器接收到这个请求。如果匹配DispatcherServlet的请求映射路径,则Web容器将该请求转交给DispatcherServlet处理。
- DispatcherServlet接收到这个请求后,将根据请求的信息(包括URL、HTTP方法、请求报文头、请求参数、Cookie等)及HandlerMapping的配置找到处理请求的处理器(Handler)。可将HandlerMapping看做路由控制器,将Handler看做目标主机。值得注意的是,在Spring MVC中并没有定义一个Handler接口,实际上任何一个Object都可以成为请求处理器。
- 当DispatcherServlet根据HandlerMapping得到对应当前请求的Handler后,通过HandlerAdapter对Handler进行封装,再以统一的适配器接口调用Handler。HandlerAdapter是Spring MVC框架级接口,顾名思义,HandlerAdapter是一个适配器,它用统一的接口对各种Handler方法进行调用。
- 处理器完成业务逻 辑的处理后,将返回一个ModelAndView给DispatcherServlet,ModelAndView包含了视图逻辑名和模型数据信息。
- ModelAndView中包含的是“逻辑视图名”而非真正的视图对象,DispatcherServlet借由ViewResolver完成逻辑视图名到真实视图对象的解析工作。
- 当得到真实的视图对象View后,DispatcherServlet就使用这个View对象对ModelAndView中的模型数据进行视图渲染。
- 最终客户端得到的响应消息可能是一个普通的HTML页面,也可能是一个XML或JSON串,甚至是一张图片或一个PDF文档等不同的媒体形式。
Spring Boot
Spiring Boot的启动过程
1、获取SpringApplicationListener监听器
2、启动所获取到的所有监听器
3、初始化ConfigurableEnvironment
4、打印Banner图标
5、创建容器ConfigurableApplicationContext
6、准备容器ConfigurationApplicationContext
7、初始化容器ConfigurationApplicationContext
8、监听器通知容器启动完成
9、监听器通知容器正在运行
Spring Boot的自动装配过程
1、通过@EnableAutoConfiguration注解开启自动配置,
2、加载spring.factories中注册的各种AutoConfiguration类,
3、当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),
4、并注入Spring容器,就可以完成依赖框架的自动配置
MyBatis
$和#的区别
:预编译sql,?占位符
$:普通sql
中间件
nginx
Dubbo
Zookeeper
MQ
kafka
ElasticSearch
微服务
微服务是一种架构,这种架构是将单个的整体应用程序分割成更小的项目关联的独立的服务。一个服务通常实现一组独立的特性或功能,包含自己的业务逻辑和适配器。各个微服务之间的关联通过暴露api来实现。这些独立的微服务不需要部署在同一个虚拟机,同一个系统和同一个应用服务器中。
优点:
- 服务原子化拆分,独立打包、部署和升级,保证每个微服务清晰的任务划分,利于扩展。
- 微服务之间采用RESTful等轻量级Http协议相互调用。
- 服务各自有自己单独的职责,服务之间松耦合,避免因一个模块的问题导致服务崩溃
缺点:
- 分布式系统开发的技术成本高(容错、分布式事务等)。
- 服务治理和服务监控关键。
- 多服务运维难度,随着服务的增加,运维的压力也在增大
Spring Cloud
数据结构与算法
冒泡排序
选择排序
插入排序
快速排序
二分查找
设计模式
六大原则
单一职责原则:
开闭原则:
定义:一个软件实体应当对扩展开放,对修改关闭。这个原则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展,即应当可以在不必修改源代码的情况下改变这个模块的行为。
里氏替换原则:
迪米特法则:
接口隔离原则:
依赖倒置原则:
单例模式
饿汉式
懒汉式
public class Singleton { private static Singleton uniqueInstance = null; private Singleton() {} public synchronized static Singleton getUniqueInstance() { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } return uniqueInstance; } }
双重校验锁
public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() {} public static Singleton getUniqueInstance() { if (uniqueInstance == null) { synchronized (Singleton.class) { if (uniqueInstance == null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
代理模式
静态代理
动态代理
工厂模式
计算机网络
从输入URL到看到页面发生了什么?
- DNS解析
- 发起TCP连接(TCP与UDP分别是什么,区别,应用场景,TCP的三次握手)
- 发送HTTP请求(http和https协议,区别,http请求头)
- 服务器处理请求并返回HTTP报文(http响应报文)
- 浏览器解析渲染页面
- 连接结束(TCP的四次挥手)
OSI七层模型
表示层和会话层 已弃用
三次握手
三次握手过程:
建立TCP连接时,需要客户端和服务器共发送3个包。
- 第一次:客户端发送初始序号seq=x和syn=1请求标志
- 第二次:服务器发送请求标志syn,发送确认标志ACK,发送自己的序号seq=y,发送客户端的确认序号ack=x+1
- 第三次:客户端发送ACK确认号,发送自己的序号seq=x+1,发送对方的确认号ack=y+1
四次挥手
TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
四次挥手过程:
- 第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态
- 第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
- 第三次挥手:客户端收到服务器确认结果后,进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)
- 第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。
TIME-WAIT,2MSL
出现 TIME_WAIT的状态原因
TIME_WAIT状态之所以存在,是为了保证网络的可靠性。由于TCP连接是双向的,所以在关闭连接的时候,两个方向各自都需要关闭。先发FIN包的一方执行的是主动关闭,后发送FIN包的一方执行的是被动关闭。主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留2MSL时长。如果Server端一直没有向client端发送FIN消息(调用close() API),那么这个CLOSE_WAIT会一直存在下去。
MSL概念
其指的是报文段的最大生存时间。如果报文段在网络中活动了MSL时间,还没有被接收,那么就会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是2分钟,不过Linux中,通常是半分钟。
TIME_WAIT持续两个MSL的作用
首先,可靠安全地关闭TCP连接。比如网络拥塞,如果主动关闭方最后一个ACK没有被被动关闭方接收到,这时被动关闭方会对FIN进行超时重传,在这时尚未关闭的TIME_WAIT就会把这些尾巴问题处理掉,不至于对新连接及其他服务产生影响。
其次,防止由于没有持续TIME_WAIT时间导致的新的TCP连接建立起来,延迟的FIN重传包会干扰新的连接。
为什么三次握手和四次挥手
- 三次握手时,服务器同时把ACK和SYN放在一起发送到了客户端那里
- 四次挥手时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发送。
TCP与UDP的区别
TCP | UDP | |
---|---|---|
连接 | 面向连接 | 无连接 |
服务对象 | 点对点的两点间服务 | 一对一、一对多、多对一、多对多 |
可靠性 | 可靠交付:无差错,不丢失,不重复,按序到达 | 尽最大努力交付,不保证可靠交付 |
拥堵控制/流量控制 | 有拥堵控制和流量控制保证数据传输的安全性 | 没有拥堵控制,网络拥堵不会影响主机的发送效率 |
报文长度 | 动态报文长度 | 面向报文,不合并,不拆分,保留上面传下来报文的边界 |
首部开销 | 首部开销大(20个字节) | 首部开销小(8个字节) |
适用场景 | 数据完整性需让位与通信实时性 | 无需保证数据完整性 |
优点 | 可靠、稳定 | 块、比TCP稍安全 |
缺点 | 慢,效率低,占用系统资源高,易被攻击 | 不可靠,不稳定 |
如何让UDP可靠
- 提高超时重传,能避免数据报丢失
- 提高确认序列号,可以对数据报进行确认和排序
实现方式:
- 将实现放到应用层,然后类似于TCP,实现确认机制、重传机制和窗口确认机制
- 给数据包进行编号,按顺序接收并存储,接收端收到数据包后发送确认信息给发送端,发送端接收到确认信息后继续发送,若接收端接收的数据不是期望的顺序编号,则要求重发;(主要解决丢包和包无序的问题)
已经实现的可靠UDP
RUDP(可靠数据报传输协议)
RTP(实时传输协议)
UDT(基于UDP的数据传输协议,是一种互联网传输协议)
HTTP1.0和HTTP1.1的区别
- 缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
- 带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
- 错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
- Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
- 长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。
HTTP与HTTPS的区别
HTTP的URL是由“
http://
”起始与默认使用端口80HTTPS的URL则是由“
https://
”起始与默认使用端口443HTTPS需要到CA申请证书,一般免费的比较少,需要收费。
HTTP协议运行在TCP之上,所有传输的内容都是明文
HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。
GET和POST的区别
使用上的区别:
GET使用URL或Cookie传参,而POST将数据放在BODY中”,这个是因为HTTP协议用法的约定。
GET方式提交的数据有长度限制,则POST的数据则可以非常大”,这个是因为它们使用的操作系统 和浏览器设置的不同引起的区别。
POST比GET安全,因为数据在地址栏上不可见”,这个说法没毛病,但依然不是GET和POST本身的 区别。
本质区别 :
GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是。这个是它们本质区别。
幂等性是指一次和多次请求某一个资源应该具有同样的副作用。简单来说意味着对同一URL的多个请求
应该返回同样的结果。
操作系统
进程与线程的区别
一个线程从属于一个进程;一个进程可以包含多个线程。
一个线程挂掉,对应的进程挂掉;一个进程挂掉,不会影响其他进程。
进程是系统资源调度的最小单位;线程CPU调度的最小单位。
进程系统开销显著大于线程开销;线程需要的系统资源更少。
进程在执行时拥有独立的内存单元,多个线程共享进程的内存,如代码段、数据段、扩展段;但每个线程拥有自己的栈段和寄存器组。
进程切换时需要刷新TLB并获取新的地址空间,然后切换硬件上下文和内核栈,线程切换时只需要切换硬件上下文和内核栈。
通信方式不一样。
进程适应于多核、多机分布;线程适用于多核
进程切换与线程切换的区别
进程切换涉及到虚拟地址空间的切换而线程切换则不会。
因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
进程间通信方式
匿名管道、命名管道、信号、消息队列、共享内存、信号量、Socket。
死锁
两个或两个以上的线程在执行过程中,因争夺锁资源而造成的一种相互等待的现象。
四个条件:
互斥条件(无法破坏):线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放
不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源
循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞
悲观锁
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁
乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。
Linux
常用命令
Shell编程
测试
测试用例的设计方法
- 等价类划分法
- 边界值分析法
- 场景法
- 错误猜想法