文章目录
- 前言
- 1.Redis数据结构及其使用场景
- 2.Redis缓存穿透、击穿、雪崩如何发生及解决方案
- 3.SpringBoot启动类注解的底层实现原理
- 4.docker常用命令
- 5.Linux常用命令
- 6.Java线程实现的几种方式
- 7.Mybatis框架中#{}和${}的区别
- 8.MySQL常见索引和区别
- 9.乐观锁怎么实现
- 10.SpringCloud组件
- 11.负载均衡的作用
- 12.并行和并发的区别
- 13.HashMap和Hashtable的区别
- 14.Runnable和Callable的区别
- 15.Spring自动注入
- 16.@Autowired和@Resource的区别
- 17.SpringMVC执行流程
- 18.Java中的异常处理
- 19.Spring异常处理
- 20.Spring框架中的常用类
前言
本文为朋友2024年一次实习面试试题记录,很多都是基础八股文。
1.Redis数据结构及其使用场景
Redis支持5种数据类型作为其value,Redis的key都是字符串类型的。
- string:Redis中字符串value最大可为512M。可以用来做一些计数功能的缓存(也是实际工作中最常见的)。
- list:简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部(左边)或者尾部(右边),其底层实现是一个链表。可以实现一个简单消息队列功能,做基于Redis的分页功能等。
- set:是一个字符串类型的无序集合,可以用来进行全局去重等。
- sorted set:是一个字符串类型的有序集合,给每一个元素一个固定的分数score来保持排序。可以用来做排行榜应用或者进行范围查找等。
- hash:键值对集合,是一个字符串类型的key和value的映射表,也就是说其存储的value是一个键值对(key-value),可以用来存放一些具有特定结构的信息。
其实,Redis还支持三种特殊的数据类型,分别是BitMap、Geo和HyperLogLog。一般情况下,可以认为Redis的支持的数据类型有上述5种。其底层数据结构包括:简单动态字符串、链表、字典、跳表、整数集合以及压缩列表。
2.Redis缓存穿透、击穿、雪崩如何发生及解决方案
- 缓存雪崩:在高并发下,大量的缓存key在同一时间失效,导致大量请求落到数据库上,如活动系统里面同时进行这非常多的活动,但在某个时间点所有的活动缓存全部过期。
解决方案:
①缓存数据的过期时间设置随机,防止同一时间大量数据过期现象的发生。
②如果缓存数据库是分布式部署,将热点数据均匀分布在不同高的缓存数据库中。
③设置热点数据永远不过期。 - 缓存穿透:访问一个不存在的key,缓存不起作用,请求会穿透到DB,流量大时DB会挂掉。
解决方案:
①接口增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截。
②从缓存取不到的数据,在数据库中也没有取到,这时可以将key-value对写为key-null,缓存有效时间可以设置短点,如30s。这样可以防止攻击用户反复用同一个id暴力攻击。 - 缓存击穿:一个存在的key,在缓存过期的那一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案:
①设置热点数据永远不过期。
②加互斥锁:简单来说,就是在缓存失效的时候(判断拿出来的值是否为空),不是立即去加载数据库,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis 的 SETNX)去set一个mutex key,当操作返回成功时,再进行加载数据库的操作并回设缓存;否则,就重试整个get缓存方法。
3.SpringBoot启动类注解的底层实现原理
来源:Spring Boot 原理解析
4.docker常用命令
来源:docker常用命令大全
- 基础命令
启动docker:
systemctl start docker
关闭docker:systemctl stop docker
重启docker:systemctl restart docker
docker设置随服务启动而自启动:systemctl enable docker
查看docker 运行状态(如果是在运行中 输入命令后 会看到绿色的active):systemctl status docker
查看docker 版本号信息:docker version
和docker info
帮助命令:docker --help
- 镜像命令
查看自己服务器中docker 镜像列表:
docker images
搜索镜像:docker search 镜像名
如:docker search --filter=STARS=9000 mysql 搜索 STARS >9000的 mysql 镜像
拉取镜像 不加tag(版本号) 即拉取docker仓库中 该镜像的最新版本latest 加:tag 则是拉取指定版本:docker pull 镜像名
和docker pull 镜像名:tag
运行镜像:docker run 镜像名
和docker run 镜像名:Tag
删除镜像:
- #删除一个:
docker rmi -f 镜像名/镜像ID
- #删除多个 其镜像ID或镜像用用空格隔开即可:
docker rmi -f 镜像名/镜像ID 镜像名/镜像ID 镜像名/镜像ID
- #删除全部镜像 -a 意思为显示全部, -q 意思为只显示ID:
docker rmi -f $(docker images -aq)
强制删除镜像:
docker image rm 镜像名称/镜像ID
保存镜像:docker save 镜像名/镜像ID -o 镜像保存在哪个位置与名字
加载镜像:docker load -i 镜像保存文件位置
- 容器命令
查看正在运行容器列表:
docker ps
查看所有容器:docker ps -a
运行一个容器:# -it 表示 与容器进行交互式启动 -d 表示可后台运行容器 (守护式运行) --name 给要运行的容器 起的名字 /bin/bash 交互路径:docker run -it -d --name 要取的别名 镜像名:Tag /bin/bash
查看容器日志:docker logs -f --tail=要查看末尾多少行 默认all 容器ID
- 运维命令
查看docker工作目录:
sudo docker info | grep "Docker Root Dir"
查看docker磁盘占用总体情况:du -hs /var/lib/docker/
5.Linux常用命令
grep
:Global Regular Expression Print,可以使用正则表达式搜索文本,并把匹配的行打印出来。
示例:查找文件file.log中“password”字段,并且统计出现的次数:
grep "password" file.log|wc -|
或者grep "password" file.log -c
awk
:将一行分为多个字段做处理。
示例1:去掉第一列
awk -F "," '{print $2, $3}' test.txt
示例2:对第一列求和
awk '{a+$1}END{print a}' test.txt
示例3:去掉列数不为3的列
awk -F "," '{if(NF == 3){print $0}}' test.txt
ps
:默认只会显示运行在当前控制台下的属于当前用户的进程。
显示所有进程
ps -A
和ps -e
显示完整格式的所有进程
ps -ef
指定进程名,找出进程名中包括java的所有进程
ps -ef | grep 'java'
top
:实时监测进程,输出的第一部分显示系统的概括。
ps
和top
命令的区别:
ps
看到的是命令执行瞬间的进程信息,而top
可以持续监视ps
只是查看进程,而top还可以监视系统性能,如平均负载、CPU和内存的消耗top
可以操作进程,如改变优先级(命令r)和关闭进程(命令k)ps
主要是查看进程的,关注点在于查看需要查看的进程top
主要看CPU,内存使用情况即占用资源最多的进程由高到低排序,关注点在于资源占用情况。
sed
:利用脚本处理文本文件。可以依照脚本的指令来处理、编辑文本文件。sed主要用来自动编辑一个或者多个文件、简化对文件的反复操作、编写转换程序等。
可以将文件的第二行和第三行裁剪出来
sed -n '2,3p' test.txt
sort
:对文本文件进行排序
正序排序
sort -n test.txt
反序排序
sort -nr test.txt
tail
和head
命令
查看文件的最后2行
tail -n 2 file.log
实时查看文件的后边追加的部分
tail -f file.log
查看文件的开始2行
head -n 2 file.log
6.Java线程实现的几种方式
我这边列举了三种,更详细的介绍请参考:【Java】详细介绍Java实现线程的四种方式
- 继承自 Thread类:通过继承 Thread 类,可以创建⼀个新的线程。为了实现线程的执⾏逻辑,需要重写 run() 方法。
- 实现 Runnable接口
- 使用 Executor 框架:Executor 框架是 Java 并发编程中的高级工具,它提供了⼀种更为灵活的方式来管理和执⾏线程。通过 Executor ,可以将任务提交给线程池,由线程池来管理线程的⽣命周期和执行。
7.Mybatis框架中#{}和${}的区别
MyBatis中能用#就尽量不要使用$符号,它们的区别主要体现在以下几点:
- #将传入的数据都当做一个字符串,会对自动传入的数据加一个双引号
- $符号将传入的数据直接显示在生成的SQL语句中
- #存在预编译的过程,对问号赋值,防止SQL注入
- $符号是直译的方式,一般用在order by ${列名} 语句中
8.MySQL常见索引和区别
来源:MySQL常见索引和区别
MySQL索引常见的分类包括:普通索引、唯一索引、主键索引、全文索引等。
- 普通索引:普通索引是最基本的索引类型,不包含任何约束和限制条件。仅仅是为了提高查询速度而建立的索引。当我们使用 WHERE 字句进行搜索时,MySQL 会使用普通索引来定位符合条件的行。如果查询条件使用到了索引字段,那么 MySQL 可以更快速地定位所需的数据。普通索引可以在字符型、数字型、日期型等各种基本数据类型上创建。
- 唯一索引:唯一索引与普通索引相同,也是为了提高查询速度而建立的索引。唯一索引不允许其字段中出现重复的值,因此,唯一索引可以用于任何必须具有唯一性的字段上。MySQL 在执行 INSERT 和 UPDATE 操作时,会对唯一索引进行检测以确保其值的唯一性。
- 主键索引:主键索引是一种特殊的唯一索引,它具有唯一性约束,但是,主键索引要求所有索引列都不为空,即设置了 NOT NULL 约束。主键索引也是最常用的索引类型,主键索引可以单独一列或多列组成。
- 全文索引:全文索引是 MySQL 中针对文本类型数据(如文本、字符型、甚至二进制类型数据)的特殊索引。它可以帮助我们更快地查询到数据,主要用于诸如文章、博客、评论、产品描述等等文本信息的快速匹配。全文索引支持中文分词,并且可以确定文本中关键词合适的位置,从而快速定位相关文本。
9.乐观锁怎么实现
乐观锁就是对数据冲突保持乐观点态度,认为不会有其他线程同时修改数据。因此乐观锁不会上锁,只是在更新数据都时候判断是否有其他线程更新,如果没有其他线程修改则更新数据,有其他线程修改则放弃数据,重新读取数据处理。Java中的乐观锁主要有两种实现方式:CAS(Compare and Swap)和版本号控制。(乐观态度+数据更新)
- CAS: CAS是实现乐观锁的核心算法,它通过比较内存中的值是否和预期的值相等来判断是否存在冲突。如果存在,则返回失败;如果不存在,则执行更新操作。CAS 它包含了 3 个参数:V(需要更新的变量)、E(预期值)和 N(最新值)。只有当需要更新的变量等于预期值时,需要更新的变量才会被设置为最新值,如果更新值和预期值不同,则说明已经有其它线程更新了需要更新的变量,此时当前线程不做操作,返回 V 的真实值。
- 版本号控制:版本号控制是乐观锁的另一种实现方式。每当一个线程要修改数据时,都会先读取当前的版本号或时间戳,并将其保存下来。线程完成修改后,会再次读取当前的版本号或时间戳,如果发现已经变化,则说明有其他线程对数据进行了修改,此时需要回滚并重试。
10.SpringCloud组件
- 服务发现——Netflix Eureka
- 客服端负载均衡——Netflix Ribbon
- 断路器——Netflix Hystrix
- 服务网关——Netflix Zuul
- 分布式配置——Spring Cloud Config
11.负载均衡的作用
- 当集群里的1台或者多台服务器down的时候,剩余的没有down的服务器可以保证服务的继续使用
- 使⽤了更多的机器保证了机器的良性使用,不会由于某一高峰时刻导致系统cpu急剧上升
12.并行和并发的区别
- 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
- 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 并行是在一台处理器上“同时”处理多个任务,并发是在多台处理器上同时处理多个任务,并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
13.HashMap和Hashtable的区别
- HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的
- HashMap允许null作为key;Hashtable不允许null作为key,Hashtable的value也不允许为null
14.Runnable和Callable的区别
来源:runnable 和 callable 有什么区别?
- 方法不同
Runnable接口只有一个run()方法,该方法不返回任何值,因此无法抛出任何checked Exception。
Callable接口则有一个call()方法,它可以返回一个值,并且可以抛出一个checked Exception。 - 返回值不同
Runnable的run()方法没有返回值,只是一个void类型的方法。
Callable的call()方法却必须有一个返回值,并且返回值的类型可以通过泛型进行指定。 - 异常处理不同
在Runnable中,我们无法对run()方法抛出的异常进行任何处理。
但在Callable中,自定义的call()方法可以抛出一个checked Exception,并由其执行者Handler进行捕获并处理。 - 使用场景不同
Runnable适用于那些不需要返回值,且不会抛出checked Exception的情况,比如简单的打印输出或者修改一些共享的变量。
Callable适用于那些需要返回值或者需要抛出checked Exception的情况,比如对某个任务的计算结果进行处理,或者需要进行网络或IO操作等。在Java中,常常使用Callable来实现异步任务的处理,以提高系统的吞吐量和响应速度。
15.Spring自动注入
详情查看:Spring自动注入
- 在 Spring 配置文件中对象名和 ref=”id”id 名相同使用自动注入,可以不配置
- 两种配置办法
① 在中通过 autowire=”” 配置,只对这个生效
②在中通过 default-autowire=””配置,表当当前文件中所有都是全局配置内容
③autowire=”” 可取值
no: 不自动注入
byName: 通过名称自动注入.在 Spring 容器中找类的 Id
byType: 根据类型注入 注意这里如果有两个相同的注入类型会报错
constructor: 根据构造方法注入
16.@Autowired和@Resource的区别
来源:【最详细】@Autowired 和 @Resource 的区别
- @Autowired 是spring提供的注解,@Resource 是JDK提供的注解
- @Autowired 默认的注入方式是ByType(根据类型进行匹配),@Resource 默认的注入方式是 ByName (根据名称进行匹配)
- 当一个接口存在多个实现类的情况下,@Autowired 和 @Resource都需要通过名称才能匹配到对应Bean。@Autowired可以通过@Qualifier来显示指定的名称,@Resource 可以通过name来显示指定名称
17.SpringMVC执行流程
来源:SpringMVC执行流程
SpringMVC执行流程:
- 用户发送请求至前端控制器DispatcherServlet;
- DispatcherServlet收到请求调用处理器映射器HandlerMapping;
- 处理器映射器根据请求url找到具体的处理器,生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet;
- DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
- 执行处理器Handler(Controller,也叫页面控制器);
- Handler执行完成返回ModelAndView;
- HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器;
- ViewReslover解析后返回具体View;
- DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中);
- DispatcherServlet响应用户。
18.Java中的异常处理
主要是try-catch-finally和throw、throws关键字。
异常都是派生于Throwable类的一个实例,这个实例可以由JVM产生,也可以再程序中手动创建,用throw手动抛出。throw的对象必须是派生于Throwable类的实例,其他类型无法通过编译。受检异常只有两种选择:要么被捕获处理,要么抛出让调用者处理。而非受检异常没有此强制要求。
- throws是用来声明异常,只要是派生于Throwable类的都可以被声明。
- try-catch-finally用来捕获异常。
所有派生于Throwable类都可以通过 catch 捕获,try 中放可能存在异常的方法,如果在 try语句块中的任何代码抛出了⼀个在 catch 子句中说明的异常类,那么程序将跳过 try 语句块的其余代码,并且执行 catch 子句中的处理器代码。如果在 try 语句块中的代码没有拋出任何异常,那么程序将跳过 catch 子句,如果方法中的任何代码拋出了⼀个在 catch 子句中没有声明的异常类型,那么这个方法就会立刻退出。在⼀个 try 语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以为每个异常类型使用一个单独的 catch 子句。需要注意,如果多个catch中的异常非继承关系,那么catch顺序不影响结果,如果catch异常存在类继承关系,那么⼦类的catch应该放在前面,父类的在后面。 - finally⼀般⽤来关闭所占用的资源。如果代码抛出异常,就会终止剩余代码的处理,并且退出这个方法。这样可能会导致⼀些程序占用的系统并不能被正确的释放。而不管是否有异常被捕获,finally中子句的代码都会被执行,可以在这里正确的释放资源。
19.Spring异常处理
来源:Spring 异常处理
- 简单的可以通过抛出特定异常,Spring 会自动转换为对应的 HTTP 状态码,或者自定义异常,添加对应状态码注解
- 在同一个文件中编写异常处理器,单独写一个方法,添加 @ExceptionHandler(XXXException.class) 注解
- 单独编写一个统一异常处理类
- 对于 Rest 等方式中的异常,可以在异常处理方法上面添加 @ResponseStatus、@ResponseBody 注解
- 更复杂的情况可以通过返回 ResponseEntity 解决
20.Spring框架中的常用类
来源:spring框架常用的类有哪些
ApplicationContext
: 用来管理bean的配置文件并创建bean
BeanFactory
: 为创建和管理bean提供基本服务
Configuration
: 使用Java配置类来配置bean
Autowired
: 用来自动装配bean
Component
: 用来标记组件类
Service
: 用来标记业务层组件
Repository
: 用来标记数据访问层组件
Controller
: 用来标记控制层组件
RestController
: 用来标记RESTful风格的控制层组件
RequestMapping
: 用来处理请求映射
RequestBody
: 用来处理请求体
ResponseBody
: 用来处理响应体
AOP
:用来提供面向切面编程(AOP)的功能