java面试题
提示:本文记录了本人2022年碰到的面试问题以及答案,如文章中有错误欢迎评论区留言
一、容器
Hashpmap扩容
关键变量:
- capacity(
初始容量
,默认16) - loadFactor(
负载因子
,默认是0.75) - threshold (
阀值
,默认12,阈值=容量*负载因子)
扩容:
- 当元素数量超过阈值时触发扩容;
- 第一次初始扩容的长度为16
- 非第一次扩容,新容量=旧容量×2,新阈值=新容量×负载因子;负载因子不变。
Hashpmap实现原理
数据结构:数组+链表
-----数组里面存放的是链表。
调用put方法时对计算key的hashcode,hashcode相同放入同一数组,取值的时候根据key的hashcode找到数组下标然后通过equals()方法找到正确的键值对。
HashSet的实现原理
HashSet是基于HashMap实现的,HashSet的元素都存放在HashMap的key上。
三、多线程
常用线程池有哪几种
名称 | 英文 | 说明 |
---|---|---|
可缓存线程池 | newCachedThreadPool | 最大线程数为Integer.MAX_VALUE,线程池长度超过处理需要,灵活回收空闲线程,若无可回收,则新建线程,回收时间默认1分钟 |
固定长度线程池 | newFixedThreadPool | 可控制线程最大并发数,超出的线程会在队列中等待 |
周期性执行任务线程池 | newScheduledThreadPool | 可指定线程数,周期执行比如每隔 10 秒钟执行一次 |
单一线程池 | newSingleThreadScheduledExecutor | 只有一个线程执行任务,之后的线程在队列中等待,所有任务按照指定顺序执行(先进先出、先进后出) |
线程池核心参数有哪些,作用是什么
参数 | 英文 | 说明 |
---|---|---|
核心线程数 | corePoolSize | 控制核心线程数量,此线程不会被回收 |
最大线程数 | maximumPoolSize | 最大线程数 |
最大空闲时间 | keepAliveTime | 针对非核心线程,到达时间回收 |
时间单位 | TimeUnit unit | 配合最大空闲时间使用 |
拒绝策略 | RejectedExecutionHandler handler | 拒绝策略 |
阻塞队列 | BlockingQueue workQueue | 阻塞队列 |
线程工厂 | ThreadFactory threadFactory | 线程工厂 |
线程池的拒绝策略
策略 |
---|
直接丢弃,并抛出异常 |
丢弃,不抛出异常 |
被拒绝的任务直接在主线程中运行,不再进入线程池 |
丢弃任务队列中最旧未执行任务,将被拒绝任务添加到等待队列 |
进程和线程的区别
- 进程是正在执行的程序,是资源分配的
基本单元
,而线程是CPU调度的基本单元。 - 进程之间
相互独立
,不能共享资源
,一个进程至少有一个线程,同一进程的各线程共享整个进程的资源(寄存器、堆栈、上下文)。 - 线程的创建和切换开销比进程小
创建线程的方式有哪几种?
- 继承Thread类,并重写run方法。
- 实现Runnable接口,并重写run方法
- 线程池
Runnable和Callable的区别?
相同点:都需要调用Thread.start()启动线程
不同点:
- Callable 能返回执行结果,能向上抛出异常
- Runnable 不能返回执行结果、不能向上抛出异常
sleep()和wait()有什么区别?
相同点:都是暂停线程的方法
不同点:
- sleep
不释放锁
,到了时间,重新执行任务 - wait
释放锁
,到了时间,需要申请锁
,拿到锁才能执行任务。
死锁发生的场景?
多个线程争抢资源 如:两个资源a和b, 一个线程抢到 a的锁,等待b的锁, 一个线程同时抢到b的锁 等待a的锁释放。
怎么防止死锁的发生呢?
- 对资源加锁顺序一致
- 使用.wait()时传入等待时间
- 使用ReentrantLock时调用tryLock传入等待时间
线程池怎么设置合理大小
CPU密集型:核心线程数 = CPU核数 + 1
IO密集型:核心线程数 = CPU核数 * 2
并行和并发有什么区别?
并行
是指两个或者多个事件在同一时刻
发生;
并发
是指两个或多个事件在同一时间间隔
发生。
三、java其他
泛型?符号和T/E 的区别
?
可以接受任意类型
,如List<> 可以接受 同时接收string、integer、 T/E
这种只能接收一种固定
的类型要么是string 要么是其他。
代理实现的两种方式
- JDK代理-
反射机制
实现aop的动态代理,在调用具体方法前调用InvokeHandler来处理。 - CGLIB代理-使用
字节码框架
asm,通过修改字节码
生成子类
使用上的区别:
- JDK代理只能对实现接口的类生成代理
- CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
获取class的三种方式
- Class aClass = new User().getClass();
- Class aClass = Class.forName(“com.demo.User”);
- Class aClass = User.class;
类加载机制
顺序 | 摘要 | 详细 |
---|---|---|
1 | 加载 | 根据类全限定名获取类的二进制字节流,将字节流所代表的静态存储结构转化为方法区运行时数据结构,内存中生成一个代表这个类的 java.lang.CLass 对象 |
2 | 验证 | 文件格式验、元数据验证、字节码验证、符号引用验证 |
3 | 准备 | 为类的静态变量分配内存,并设置默认初始值 |
4 | 解析 | 类或接口解析、字段解析、类和接口方法解析、符号引用、直接引用 |
5 | 初始化 | 最后一个阶段,程序员可在静态块static{}中自定义赋值变量,或在构造函数中赋值 |
详情过程参考网友文章,点这里
浅拷贝和深拷贝
为什么需要浅拷贝和深拷贝呢?
@Data
@AllArgsConstructor
public static class Score{
public Integer score;
}
@Data
public static class User {
public String name;
public Score score;
}
public static void main(String[] args) {
User user = new User();
user.setName("小明");
user.setScore(new Score(10));
User user1 = user;
System.out.println((user1==user));
}
以上代码输出true,你会发现通过User user1 = user;的方式指定的还是同一个对象
如果需要不同对象该怎么做呢?
浅拷贝
这个时候就需要浅拷贝了,在要拷贝的类中实现Cloneable并重写clone方法(只需重写无其他操作)使用时通过对象.clone获取,如: User user1 = (User) user.clone();
@Data
@AllArgsConstructor
public static class Score {
public Integer score;
}
@Data
public static class User implements Cloneable {
public String name;
public Score score;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
User user = new User();
user.setName("小明");
user.setScore(new Score(10));
User user1 = (User) user.clone();
System.out.println((user1 == user));
System.out.println((user1.getScore() == user.getScore()));
}
第一行输出false,证明浅拷贝获取的对象与之前的对象非同一内存地址。
第二行输出true,证明浅拷贝获得的对象,其引用的子类依旧是同一内存地址。
注意:这里的user1变成了 User user1 = (User) user.clone();
注意:第二行输出true说明:通过对象.clone()获取到的子类依旧是同一个对象,需要子类是不同对象时需要用到
深拷贝。
深拷贝
上面说了由于浅拷贝获得的对象,其引用的子类依旧是同一内存地址,所以需要用到深拷贝。
实现方式有两种:
- 在需要拷贝子类中也实现Cloneable并重写clone方法,但子类太多就很麻烦。
- 也可以通过序列化的方式,然后再反序列回来。
public static <T extends Serializable> T clone(T obj) {
T cloneObj = null;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
ByteArrayInputStream is = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream os = new ObjectInputStream(is);
cloneObj = (T) os.readObject();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
ThreadLocal 是什么?
ThreadLocal 是线程本地存储
经典实用场景:数据库连接,session管理
ERROR和Excepition的区别
ERROR 不能捕捉和处理 如:内存溢出
Excepition 可以捕捉和处理 如:io、空指针
三、redis相关
redis的基本数据类型
类型 | 详情 |
---|---|
String | 最基本的数据类型,二进制安全的字符串,最大512M。 |
list | 按照添加顺序保持顺序的字符串列表。 |
set | 无序的字符串集合,不存在重复的元素。 |
sorted set | 已排序的字符串集合。 |
hash | key-value对的一种集合。 |
redis超出设置内存淘汰策略有哪几种
已设置过期的key:
- 最近最少使用
- 将要过期
- 随机
其他:
- 淘汰最近最少使用
- 淘汰任意
- 禁止淘汰
redis有几种删除过期key的策略呢?
- 定时删除,为每个key设置一个定时器,不推荐。
- 惰性删除,每次从键空间中获取键时判断是否过期,过期删除。
- 定期删除,每隔一段时间删除过期的key。
redis锁了解过吗?
使用setnx来做锁,再用 expire 给锁加过期时间防止锁忘记了释放。
可以同时把 setnx 和expire 合成一条指令来用(防止expire 执行之前宕机或者重启。
redis怎么大批量删除key?
使用sscan scanParams.count(500); 每次删除指定条数。
redis高可用
- 主从一个master 多个slave ,master提供增删改 slve只提供查询
- 哨兵,在主从的基础上,如果发生异常会选举
- 集群,多个mastter 多个slave
redis为什么是线程安全的?
Redis是单进程单线程的,Redis利用队列技术将并发访问变为串行访问。
四、框架相关
spring作用域
类型 | 详细 |
---|---|
singleton | 单例,默认 |
prototype | 每个bean请求提供一个实例 |
request | |
session | |
global- session |
@SpringBootApplication注解里面有啥
- springBootConfigureation 配置文件
- EnableAutoConfiguration 打开自动配置
- @ComponenetScan spring组件扫描
springBoot启动的时候运行特定的代码
实现接口ApplicationRunner 获取CommandLineRunner
springboot异常统一处理
@ControllerAdvice+@ExceptionHandler处理全局异常
bootstrap.properties和application.properties的区别?
bootstrap加载优先级更高值不会被覆盖
spring事务隔离级别
英文 | 详细 |
---|---|
REQUIRED | 默认值,支持当前事务,如果没有事务会新建事务 |
REQUIRES_NEW | 新建新事务并挂起当前事务 |
SUPPORTS | 支持当前事务,如果没有事务的话以非事务方式执行 |
MANDATORY | 支持当前事务,如果没有事务抛出异常 |
NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务则将当前事务挂起 |
NEVER | 以非事务方式进行,如果存在事务则抛出异常 |
NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作 |
@Resouce和@Autowired的区别
- @Resouce,是java自带的,可以通过name和type来获取,默认是name
- @Autowired,是spring的,只支持type,如果接口有两个实现类配合Qualifier(userImpl) 来使用
五、springcloud相关
nacos
nacos挂了a服务还能访问b服务吗
服务是怎么注册到nacos上的
服务提供者向nacos发送rest请求,带上ip地址和端口等信息,nacos接收到会保存起来
nacos是怎么知道服务健康状态的
使用心跳方式监控方式:
- nacos支持服务端主动监测提供者状态
- 服务提供者向nacos发送心跳、
默认5秒
nacos怎么判断服务挂了呢?
nacos15秒
未收到某个服务心跳,将服务设置未不健康状态,30秒
剔除服务
nacos更多详情参考网友文章
,点这里
Ribbon常用负载均衡策略
- 轮询(多个服务,123个服务依次调用,默认)
- 随机
- 权重策略(相应时长越短权重越高)
- 重试(指定超时间,按照轮询策略重试)
六、Mysql相关
InnoDB有哪几种索引类型,使用场景
类型 | 场景 |
---|---|
Hash | 等值查询,不支持范围查询 |
b+树 | 范围查询 |
hash的索引类型能做范围查询吗?
不行,因为hash类型的索引存放的key的hash值,并且hash是无序的。
什么是回表
- 普通索引,存放了对主键索引的引用
- 主键索引,存放的是完整的一条数据
使用普通索引查询时,会先查询到普通索引,再通过普通索引查询主键索引,主键索引里面就有该行的数据。
会导致索引失效的几种写法
写法 | 详细 |
---|---|
WHERE user_id=‘A’ or status =‘A’ | 引擎会放弃索引,可使用union all 进入多次查询 |
WHERE user_name LIKE ‘%A’ | 由于前面是模糊的所以不能使用索引进行查询 |
联合索引未按最左侧规则匹配 | 如索引a,b,c 只使用了bc,或者b,c进行查询,查询条件中没有a |
对索引字段使用函数 | 如 where mothe(modified)=7 |
隐式转换 | 如:查询条件输入的是数字,字段类型是varchar,可参考点击这里 |
隐式编码转换 | 如联表查询时一个字段的字符编码是utf8mb4,另一个是utf8 |
索引类型是Hash,但是使用了范围查询 | 当索引类型为Hash时不支持范围查询,因为hash是无序的 |
事务隔离级别
写法 | 详细 |
---|---|
读取未提交内容 | |
读取已提交内容 | |
可重复读 |
七、kafka
怎么防止消息丢失
情况 | 方案 |
---|---|
生产者丢失 | 代码里面发消息时使用有回调的方法producer.send(mes,callback) ,不使用producer.send(msg) |
消费者丢失 | 确保消息消费完提交,设置手动提交,enable.auto.commit 设置为false |
怎么保证两条消息都发送成功
可以使用kafka的事务。
八、mybatis
mybatis的缓存
- 一级缓存-默认开启,作用域sqlsession,一个sqlsession下同一sql执行两次走缓存。
- 二级缓存-作用域全局的,多个sqlsession共享一个缓存。
新增删除修改会清空缓存。
开启二级缓存后,优先读取二级缓存,找不到在去一级缓存中找
详情过程参考网友文章,点这里
mybatis传参时#和$的区别
#-是占位符,mybatis会预编译防止sql注入。
$-不会预编译,如order by ${name } 可传入数据库的表字段,直接由前端传入会引起sql注入。
如要传入排序字段,name 是 “time”
order by #{name} DESC 是 “time" -排序无效
order by ${name} DESC 是 time