2021年最新面试题
前言
提示:面试题知识点不分先后排序
一、集合
1.集合的特点
- 对象封装数据。集合用于存储对象
- 对象的个数确定可以使用数组,个数不确定可以用集合
- 集合可改变长度
2.常用的集合类有哪些?
Map接口和collection接口是所有集合框架的父接口:
- Comllection接口的子接口包括:Set接口和List接口
- Map接口实现类主要有:HashMap、Hashtable、ConcurrentHashMap
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
Collection集合主要有List和Set两大接口:
List | Set |
---|---|
有序 | 无序 |
插入多个null | 允许一个null |
元素索引 | 元素唯一性 |
支持for循环,通过下标遍历,可迭代 | 只能用迭代 |
查找元素效率高 | 删除和插入效率高 |
Map是一个键值,对集合、存储键,值之间的映射。
key | value |
---|---|
无序,唯一值 | 不要求有序,允许重复 |
Map没有继承Conllection接口, 从Map集合检查元素,只要给出键对象,就返回相应的值。
3.哪些集合类是线程安全的?
- vector:线程安全但是效率低。
- statck:堆栈类,先进后出。
- hashtable:就比hashmap多了个线程安全。
- enumeration:枚举,相当于迭代器。
4.Conllection接口
1.Iterator 和 ListIterator 有什么区别?
ltrator | Listterator |
---|---|
可以遍历Set和List集合 | 只能遍历List |
单向遍历 | 双向遍历 |
2.ArrayList 和 LinkedList 的区别是什么?
相同点:都不保证线程安全
ArrayList | LinkedList |
---|---|
动态数组 | 双向链表 |
效率高 | 效率低 |
不占内存 | 更占内存 |
3.ArrayList 和 Vector 的区别是什么?
ArrayList | Vector |
---|---|
非线程安全 | 线程安全 |
扩容+50% | +1% |
性能好 | 性能不行 |
二、多线程
1.并发的三要素是什么?怎么保证多线程的运行安全问题?
并发三要素:原子性、可见性、有序性。
出现线程安全问题的原因:
- 线程切换带来的原子性问题
- 缓存导致的可见性问题
- 编译优化带来的有序性问题
2.并发和并行的区别?
- 并发:多个任务在同一个cpu核上、按时间片轮流交替,从逻辑上是同时运行
- 并行:多个处理器或多个核处理器同时处理多任务。是真正意义上的同时运行
3.什么是上下文切换?
当前任务在执行完cpu时间片切换到另一个任务之前会保存自己的状态、以方便下次再切换回这个任务时候,可以再加载这个任务的状态 “任务从保存到再加载的的过程就是一次上下文切换”
4.什么是线程死锁?
线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
形成死锁的四个条件:互斥条件、请求与保持条件、不剥夺条件、循环等待条件
如何避免死锁:破坏产生死锁的四个条件之一就可以了
5.创建线程有哪几种方式?
1.继承 Thread类
定义Thread类的子类,重写run方法。
创建自定义的线程子类对象。
调用子类实例start方法启动线程
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法正在执行...");
}
}
public class TheadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行结束");
}
}
//执行结果
main main()方法执行结束
Thread-0 run()方法正在执行...
2.实现Runnable接口
定义Runnable接口实现类,并重写run方法
创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
调用线程对象的start()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
}
}
public class RunnableTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}
}
//执行结果
main main()方法执行结束
Thread-0 run()方法正在执行...
3.实现Callable接口
创建实现Callable接口的类myCallable
以myCallable为参数创建FutureTask对象
将FutureTask作为参数创建Thread对象
调用线程对象的start()方法
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() {
System.out.println(Thread.currentThread().getName() + " call()方法执行中...");
//call()方法的返回值必须与接口泛型类型一致
return 1;
}
}
public class CallableTest {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
try {
Thread.sleep(1000);
System.out.println("返回结果 " + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " main()方法执行完成");
}
}
//执行结果
Thread-0 call()方法执行中...
返回结果 1
main main()方法执行完成
4.使用Executors工具类(线程池)
主要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool,后续详细介绍这四种线程池
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run()方法执行中...");
}
}
//执行结果
线程任务开始执行
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
pool-1-thread-1 is running...
6.run()和start()有什么区别?
run()方法会当成一个main线程的普通方法执行。
start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。
7.线程的生命周期和五种基本状态?
五种基本状态:新建、可运行、运行、阻塞、死亡
8.sleep()和wait()的区别?
两者相同点:可以暂停线程的执行
sieep() | wait() |
---|---|
是Thread静态方法 | 是Object类的方法 |
释放CPU不释放锁 | 释放CPU和锁 |
用于暂停执行 | 用于线程间交互/通信 |
自动苏醒。或者使用wait()超时后线程自动苏醒 | 不会自动苏醒,要调用对象上notify()或notifyAll() |
9.sleep()和yield()的区别?
两者相同点:可以暂停线程的执行
sieep() | yield() |
---|---|
低优先级运行机会 | 更高优先级运行机会 |
转入阻塞状态 | 转入就绪状态 |
抛出异常 | 没有声明任何异常 |
三、Spring
1.Spring框架核心?
Ioc容器和AOP模块
- IOC容器管理POJO对象以及它们之间的解耦关系
- AOP以动态非侵入的方式增强服务
2.Spring用到了哪些设计模式?
- 工厂模式:用来创建对象的实例
- 单例模式:Bean默认为单例模式
- 代理模式:面向切面功能AOP用到了JDK动态代理和CGLIB字节码生成技术
- 模板方法:解决代码重复问题
- 观察者模式:定义对象键一种 一对多依赖关系。
3.Spring控制反转(IOC)
1.IOC有什么作用?
- 依赖注入(管理对象)
- 解耦
- 代理托管类的产生过程
2.IOC实现机制?
实现原理:工厂模式+反射机制
interface Fruit {
public abstract void eat();
}
class Apple implements Fruit {
public void eat(){
System.out.println("Apple");
}
}
class Orange implements Fruit {
public void eat(){
System.out.println("Orange");
}
}
class Factory {
public static Fruit getInstance(String ClassName) {
Fruit f=null;
try {
f=(Fruit)Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Client {
public static void main(String[] a) {
Fruit f=Factory.getInstance("io.github.dunwu.spring.Apple");
if(f!=null){
f.eat();
}
}
}
3.IOC支持哪些功能?
对IOC来说,重要的是容器。容器管理Bean生命周期。控制Bean依赖注入
- 依赖注入
- 依赖检查
- 自动装配
- 支持集合
- 初始化和销毁方法
- 回调某些方法(需要实现接口。)
4.Spring面向切面(AOP)
1.SpringAOP原理
AOP原理:不去动原来的代码,而是基于原来代码产生代理对象,通过代理的方法去包装,就完成对以前方法的增强
AOP底层原理就是动态代理的实现
2.代理机制有几种
- JDK动态代理
- CGLIB字节码代理
3.几大通知的执行顺序
- 前置通知:在目标方法被调用之前调用通知功能;
- 后置通知:在目标方法完成之后调用通知,
- 返回通知:成功执行之后调用通知,
- 异常通知:抛出异常后调用通知,
- 环绕通知:通知包裹了被通知的方法,
5.Spring注解
1.@Component, @Controller, @Repository, @Service 有何区别?
@Component:这将 java 类标记为 bean。它是任何 Spring 管理组件的通用构造型。spring 的组件扫描机制现在可以将其拾取并将其拉入应用程序环境中。
@Controller:这将一个类标记为 Spring Web MVC 控制器。标有它的 Bean 会自动导入到 IoC 容器中。
@Service:此注解是组件注解的特化。它不会对 @Component 注解提供任何其他行为。您可以在服务层类中使用 @Service 而不是 @Component,因为它以更好的方式指定了意图。
@Repository:这个注解是具有类似用途和功能的 @Component 注解的特化。它为 DAO 提供了额外的好处。它将 DAO 导入 IoC 容器,并使未经检查的异常有资格转换为 Spring DataAccessException。
2.Autowired 注解有什么作用?
@Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在
3.@Qualifier 注解有什么作用?
使用@Qualifier 注解和 @Autowired 通过指定应该装配哪个确切的 bean 来消除歧义。
4.@RequestMapping 注解有什么用?
类级别:映射请求的 URL
方法级别:映射 URL 以及 HTTP 请求方法
6.Spring 事物管理?
编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
四、Spring MVC
1.MVC核心组件
- 前端控制器
- 处理器映射器
- 处理器
- 视图解析器
- 视图
2.描述Spring MVC的工作流程?
(1)用户发送请求至前端控制器DispatcherServlet;
(2) DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
(4)DispatcherServlet 调用 HandlerAdapter处理器适配器;
(5)HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
3.Spring MVC常用的注解有哪些?
@RequestMapping:用于处理请求 url 映射的注解,可用于类或方法上。用于类上,则表示类中的所有响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接收http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将conreoller方法返回对象转化为json对象响应给客户。
@Controller 用于标记在一个类上,使用它标记的类就是一个Spring MVC Controller 对象
请求路径上有个id的变量值,可以通过@PathVariable来获取
@RequestParam用来获得静态的URL请求入参 spring注解时action里用到。
4.如何解决POST请求中文乱码问题
在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;
五、SpringBoost
1.核心注解是哪个?主要由哪几个注解组成的?
启动类注解@SpringBootApplication 。也是SpringBoot的核心注解,包含了以下三个注解:
@SpringBootConfiguration : 组合 @Configguration注解 实现配置文件的功能
@ComponentScan: Spring组件扫描
2.核心配置文件是什么?
spring boot 核心的两个配置文件:
- bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext加载的,比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud Config 或者 Nacos 中会用到它。且 boostrap 里面的属性不能被覆盖;
- application (. yml 或者 . properties): 由ApplicatonContext 加载,用于 spring boot 项目的自动化配置。
六、MyBatis
1.#{}和${}的区别?
#{} | ${} |
---|---|
是占位符,预编译处理 | 是拼接符,没有预编译处理 |
是以字符串传入,将sql中的#{}替换?号 | 是原值传入,替换成变量的值 |
有效防止sql注入 | 不能防止sql注入 |
自动加上单引号 | 不会加上单引号 |
替换是在DBMS中 | 替换DBMS外 |
七、Redis分布式集群
1.Redis 可以存储键和五种不同类型的值之间的映射。
- 字符串
- 列表
- 集合
- 散列表
- 有序集合
2.Redis有哪些优缺点
优点:
- 读写性能优异
- 支持俩种持久化:AOF 和 RDB
- 支持事物
- 数据结构丰富
- 支持主从复制
缺点:
- 数据库容量受到物理内存的限制
- 不具备自动容错和恢复功能
- 主机宕机未及时同步到从机
- 较难支持在线扩容
3.Redis为什么这么快
- 基于内存操作
- 数据结构简单
- 采用单线程
- 使用多路I/O 复用
4.缓存异常
1.缓存雪崩
某一时刻发生大规模缓存失效,所有请求发送到数据库,数据库承受大量请求崩掉
解决方案
- 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
- 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。
- 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
2.缓存穿透
查询缓存和数据库不存在的数据,所有请求发送到数据库上,数据库承受大量请求崩掉
解决方案
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap拦截掉,从而避免了对底层存储系统的查询压力
3.缓存击穿
大量请求查询一个key,key失效,导致大量请求打到数据库
解决方案
- 设置热点数据永远不过期。
- 加互斥锁,互斥锁
4.缓存预热
系统上线后,将相关的缓存数据直接加载到缓存系统。
解决方案
- 直接写个缓存刷新页面,上线时手工操作一下;
- 数据量不大,可以在项目启动的时候自动进行加载;
- 定时刷新缓存;
5.事物
1.什么是事物?
Redis中,单条命令是原子性执行的。事物不保证原子性、且没有回滚
Redis的事务总是具有ACID中的一致性和隔离性
2.Redis事物三个阶段?
- 事物开始MULTI
- 命令入队
- 事物执行EXEC
6.哨兵
哨兵的核心知识
- 哨兵至少需要 3 个实例,来保证自己的健壮性。
- 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis集群的高可用性。
- 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演练。
八、Mysql和Oracle的区别
区别: | Mysql | Oracle |
---|---|---|
事物: | 在存储引擎的行级锁支持事物 | 完全支持事物 |
逻辑备份: | 需要锁定数据,才能一致 | 不锁定数据,备份数据一致 |
提交方式: | 默认自动提交 | 需要手动提交 |
数据持久性: | 更新数据会丢失 | 在线日志恢复客户提交数据 |
性能诊断: | 慢查询日志 | 各种成熟诊断工具 |
一致性: | 已读提交隔离 | 可序列化隔离 |
并发性: | 使用表级锁,事物引擎的表行级锁依赖表索引 | 行级锁,对并发支持较高 |
九、单体式架构和分布式架构有什么区别?
单体式架构
优点 | 缺点 |
---|---|
开发,测试简单 | 启动慢,代码冲突 |
扩容简单 | 性能有限 |
分布式架构:业务复杂提高效率