荣耀面经

笔试回顾

1
2
3
4
5
6
7
好像过了两道题吧
前两道做出来了,第三题不怎么会,用测试用例过了15%
第一题:
好像是计算幂的累加和
第二题是计算复数的绝对值
用绝对值公式,根号下a²+b²
需要注意的是,用Math.rand进行四舍五入

1.Redis常用的数据结构

1
2
3
4
5
6
7
8
字符串String,最常见的,可以存储任何数据,字符串整形符点都行
链表list:链表结构
集合set,
有序集合zset
哈希hash key-value的形式
位图bigmap
地理位置GEO
HyperLogLog:去重统计登

2.zset的底层数据结构

1
2
3
4
5
6
7
8
9
Redis中set的底层数据结构就是hash表来实现的,这个和Java中的set一样
就是将map中的key,添加作为到set中的元素,然后value是一个固定值

有序集合zet:哈希表+跳表
哈希表跟set一样,
跳表用于维护元素的顺序,存放的时候按照顺序排序

Java中的有序集合就是TreeSet
底层是用红黑树的形式存放的

3.Redis的分布式锁和看门狗机制

1
2
3
4
5
6
7
8
9
10
11
12
Redis的分布式锁,就是使用通过setNX 互斥和setEX设置过期时间
但是基本上不用,因为还有很多缺陷
不可重入、不可重试、超时释放、主从一致性等
用的话就用依赖redission的锁,可以避免上述问题

看门狗机制是Redis锁的一种机制
防止持有锁的时候,由于业务逻辑出问题而导致没有释放锁的问题
流程就是:
1.获取到锁,对锁设置一个延迟任务
2.这个任务每隔1/3锁的过期调用,如果线程没有执行完,重置锁的过期时间为1
3.当程序出现异常,就会在锁过期后,自动锁释放锁
简单来说就是:程序没执行完,锁不释放一直延时,程序异常,锁过期释放

4.ThreadLocal的作用和原理

1
2
3
4
5
6
7
8
9
10
11
12
Thread Local本地线程嘛,就是线程可以独立的获取自己的变量副本
而不是共享变量,从而避免多线程的资源共享问题
一般就是在拦截器intercepter中去拦截,然后将当前用户的信息放进去
这样在其他地方就可以拿到用户信息
最后remove清理变量

原理:每隔线程都会维护一个ThreadLocalMap
用于存储ThreadLocal以及其数据
调用 threadLocal.set(value) 时,
当前线程的 ThreadLocalMap 会将 ThreadLocal 对象作为键,value 作为值存储起来。
获取值:调用 threadLocal.get() 时,
当前线程的 ThreadLocalMap 会通过 ThreadLocal 对象查找并返回对应的值。

5.Redis的持久化机制

1
2
3
快照文件和追加文件
快照:就是把Redis中的数据保存到磁盘上面,方便宕机的时候做数据恢复
追加:通过日志的方式,Redis每次执行写操作的时候,记录下来存储到文件中去

6.MySQL的索引结构

1
2
3
4
5
6
7
8
9
MySQL底层是通过B+树的方式实现索引的
创建索引的时候,会将当前字段插入b+树
b+树是多路平衡树
他的非叶子节点只做索引,不含数据
叶子节点包含全部的建,并且收尾互相连接,方便查找

这种结构为什么快?
1、每个节点有多个子指针,树的高度低,比较的次数就少
2、叶子节点通过链表去连接的,方便查找

7.线程池工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Java中有自带的线程池,在某些高并发的场景下,需要自定义线程池

线程池:管理线程的池子,可以容纳多个线程,省去了频繁创建线程的操作
Java中有几种常见的线程池,都是去实现了ThreadPoolExecutor这个类
用这个类的构造器,去设置参数,核心线程,最大线程,超时时长,任务队列等
可以直接用,然后重写run方法实现的

设计一个线程池嘛
1.创建N个线程
2.把任务提交给线程运行
3.执行的时候,入队
4,执行完毕,出队

使用场景:异步处理的时候要用到线程池开启线程,并发处理的时候也需要

8.进程与线程的区别

进程间通信方式,说一种熟悉的进程间通信方式

1
2
3
4
5
6
7
进程就是计算机的运行实例,一个进程中会有多个线程
进程间相互独立,线程间共享进程的资源等

进程中的通信方式:共享内存,允许多个进程访问同一块内存区域
线程中的通信方式:共享变量,成员变量
使用wait方法进入等待状态,等其他线程notify通知
或者使用消息队列去传递消息

9.设计模式

1
2
3
4
5
6
7
8
9
10
设计模式就是通用的一种代码规范和解决方式
可以提高代码的可维护和复用性等
单例模式:spring中的bean是单例的,通过IOC容器对Bean管理,保证整个应用只有一个Bean实例
工厂模式:springIOC负责容器的创建和管理实例,可以将IOC看做是一个工厂
我们不需要知道对象是如何创建的
只需要用注解直接注入对象就行

代理模式:spring的AOP面相切面编程就用到了代理模式
实现proxy类的方法,生成代理对象,在其他地方通过代理对象去调用方法

10.排序算法

1
2
3
4
5
6
7
冒泡,选择,插入,堆排序等
比较熟悉的冒泡
每次交换相邻的元素,复杂度是n2
快速排序的思想是,选一个基准元素,将数组分为两个子数组
小于基准的,大于基准的
然后递归的对两个子数组排序
直到有序

11.设计一个高并发系统

1
2
3
4
5
6
7
1.首先要从需求层面明确我的并发量,再决定去采用什么样的解决方案
2.系统架构方面,可以将系统功能拆分为多个微服务,每个服务独立开发部署
3,用负载均衡将请求分发到多个服务器中,防止一个过载
4.高并发肯定要用到缓存了,先讲数据放入缓存中预热,减轻数据库压力
5.数据库的话,就是分库分表,写的操作集中在主库,读的操作在分库,减轻数据库压力
6.消息队列,对耗时的业务通过消息队列放入后台异步处理等
7.设置熔断限流去控制流量等

12.KMP算法

1
2
3
4
我知道是一种字符串匹配的高效算法
构建一个前缀表,去确定匹配的位置
然后匹配过程中,通过前缀表快速匹配到下一个位置,而不是从头开始
主要是利用部分的匹配信息,避免在匹配失败的时候,重新开始

13.tcp长链接短链接

1
2
3
4
5
6
7
8
9
10
长连接在客户端和服务器之间建立一次连接后
,保持连接的持续存在,除非主动断开或超时。
TCP短连接:
每次通信完成后立即关闭连接,下次通信时重新建立新的连接。

长链接通道判断:
我记得有一个心跳机制
固定某一段时间去检查连接的状态
从而判断是否连接

14.面向对象设计的6大原则

1
2
我只记得其中几个,单一职责,开放闭合
原因:高内聚,低耦合,易于扩展,提高可维护性等

15.OOM怎么去定位解决?

1
2
3
4
5
6
7
8
内存溢出嘛,首先看控制台去判断溢出的类型,
是堆空间不足,还是老年代溢出,还是其他


可以去设置jvm的参数去监控内存
去监控内存对象的,分析内存的使用情况
然后可以分配一个大内存去解决
或者优化数据结构

16.Class的底层,反射的原理

1
2
3
4
5
6
7
编译的过程中,会将Java的源代码编译成字节码文件就是.class文件
然后jvm通过类加载器,读取.class文件,生成Class文件
这个大Class中就包含类的所有信息,如类名、构造方法、字段和方法等。

反射允许程序在运行的时候,动态的获取类的所有信息,执行方法
原理就是获取大Class对象,然后通过reflect包下的方法
去动态的调用这些方法来实现功能

17.类的加载机制

1
2
3
4
5
6
7
JVM 的类加载机制是动态的,能够在运行时根据需要加载类,而不是一开始就加载所有类
加载:通过类的全限定名,查找此类的字节码文件,利用字节码文件创建Class对象

加载机制:双亲委派机制
当一个类加载器收到加载的请求的时候,会将这个请求给父类加载器去处理
如果父类无法加载,才由当前类加载
好处是:避免重复加载

18.多态的理解

1
2
3
4
5
编译时多态:通过方法重载实现,发生在编译阶段,
依据方法参数的类型和数量选择合适的方法。
父类的引用指向子类的对象
运行时多态:通过方法重写实现,发生在运行阶段,
依据对象的实际类型来决定调用哪个方法。

19.树的深度优先和广度优先遍历

1
2
3
4
5
6
7
8
9
10
深度优先就是,访问到叶子节点,再回溯上一节点遍历
前序:中左右
中序:左中右
后续:左右中
广度优先就是:层序遍历,一层一层的往下遍历

红黑树:红黑树是一种自平衡的二叉搜索树,每个节点存储一个额外的颜色位
适合频繁插入和删除的操作场景
hashmap:数组+链表+红黑树
treemap,TreeSet:红黑树

20.网络模型和常见的位置

1
2
3
4
5
6
7
8
常见的网络模型主要是分为osi七层模型,和tcpip四层模型
osi七层就是:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
tcpip技术:应用层,传输层,网络层,网路接口层
其实都是对应的关系
http、https:网页传输协议,在应用层
tcp:通过三次握手建立连接,在传输层,可靠连接
udp:不可靠连接,快速连接,传输层
IP:网络层

21.synchronized修饰静态方法和实例方法有什么不一样

1
2
3
4
5
线程同步:当多个线程同时访问共享资源(例如变量、对象)时,
使用 synchronized来防止线程之间的竞争,确保同一时间只有一个线程能访问该资源。

修饰实例方法的话,针对的是实例,多个线程可以访问不同对象的
修饰静态方法的话:静态方法是全局的,所以只有一个线程能访问

22.对java原子性操作的理解

1
2
3
4
5
原子性:要么全部成功,要么全部失败
执行的过程中不能被其他线程干扰
我们可以使用加锁synchronized修饰去得到原子性
也可以使用Java中内带的一些原子类
AutomicInteger等

面试回答不太好的

1.注解底层实现,编译型注解,运行时注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
注解是一个代码说明
就是在源代码中提供了一种添加补充机制
注解可以在编译时,运行时使用

注解的底层原理是通过Java中的反射实现的
反射机制允许程序在运行的时候,动态的获取类的信息,操作类的字段
当使用注解的时候,编译器将会将注解的信息保存到编译后的字节码文件中
这样程序运行后,通过反射就会读取到这些注解,并根据注解的信息去处理

注解的格式:
public @interface 名称{
public 属性类型 属性名称() default 默认值;
}
编译型注解:在编译的时候去给编译器提供信息
@override @Deprecated
底层实现:编译时注解通常通过 Java 编译器(如 javac)进行处理。
编译器在编译阶段识别这些注解,进行静态分析,并根据注解提供的元数据产生相应的编译错误或警告。
处理方式:编译时注解一般不会被包含在字节码中,除非被显式定义为 @Retention(RetentionPolicy.SOURCE)。

运行时注解:通过反射去获取

元注解:@Retention:指定注解的生命周期
@Target:指定注解的目标元素

自定义注解:

1
2
3
4
5
6
7
8
9
10
11
就是通过@interface关键字去自定义注解
然后通过元注解去标记,当前注解的作用域
@Target(ElementType.METHOD)//当前注解作用在方法上
@Retention(RetentionPolicy.RUNTIME)//表示是运行时注解

我在项目中有用到过自定义注解去做删除的一个日志记录
先自定义注解后,用于标记需要记录日志的方法
创建切面类,@Aspect,
在切面类中通过前置通知@before,后置通知after,环绕通知around
去将我的去将我的自定义注解引用进来
在方法中实现插入删除日志的业务逻辑就行

2.Redis,删除,延迟双删

1
2
3
4
5
6
7
8
9
延迟双删是一种常见的缓存失效策略
1.从数据库中删除数据后,立即从缓存中删除
2.延迟一段时间(确保系统的异步操作能完成)
3.再次从缓存中删除对应的数据
为什么要缓存的两次删除?
1.在分布式系统中,数据库和缓存的操作是异步的
但我们从数据库中删除数据的时候,可能缓存数据不会立即更新
所以通过引入延迟,来给系统一些时间处理异步请求
然后再删除确保数据真正删除了

3.差不多这些