面试总结
2月8号面试(凉面)
1.项目中使用了token,token的结构是什么,你的token是怎样生成的?
token由三部分组成:由头部(header),有效载荷(palyload) 和 签名(signature)组成.
header 由 typ 和 alg 组成,alg 是 HS256,表示token是使用 HS256 算法进行加密的。
playload 里面是承载要传递的数据。
signature 是把header,payload 分别进行 base64 编码,然后拼接在一起,作为分隔符。
我的token是用用户的唯一id和 username 生成的。
2.为什么使用token?token为什么安全,我拿到你的token,是不是就可以登录?
使用Token的目的是为了验证用户登录情况以及减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮。
如果涉及到修改数据导致token发生改变,那么拿到改变后的token,与之前存储的token进行对比,发现与之前存储的token不相同,则不会进行下一步的操作。
可以使用ip进行限制,如果ip不同了,先提示是否在xxx地进行登录,本人进行登录以达到限制登录的功能
3.token除了放在缓存中,还可以放在其他地方嘛?
token除了放在redis缓存中,还可以放在其他地方,比如:
- 可以放到数据库中,可以确保Token的持久化。
- 可以存储在Cookie中,这样可以在客户端和服务端之间传递Token,但是这样的缺点是Cookie存在被盗取和篡改的可能。
- Token可以存储在浏览器的LocalStorage或SessionStorage中,这样可以在客户端中保存Token,并且可以在需要时获取Token。但是,使用LocalStorage和SessionStorage存储Token需要注意安全性,避免被XSS攻击和其他安全问题。
3.项目中使用到了redis,那redis有哪些数据结构?(这个问题要回答5种数据结构的底层实现原理)
最常用的有5种:字符串(String),哈希(Hash),列表(list),集合(set),有序集合(ZSET)
String(String 字符串)
String是简单的 key-value 键值对,value 不仅可以是 String,也可以是数字。它是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
Hash(HashMap,哈希映射表)
Hash 对应的 Value 实际上内部是一个HashMap,有两种不同的实现:这个Hash的成员比较少的时候,就会采用类似一维数组的方式来存储,而不会使用HashMap的结构,当成员数量增大时会自动转成真正的 HashMap。
list(双向链表)
list 的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构
set(HashSet)
set 的内部实现是一个 value 永远为 null 的 HashMap,实际就是通过计算 hash 的方式来快速排重的,这也是 set 能提供判断一个成员是否在集合内的原因。
ZSET(插入有序Set集合)
内部使用 HashMap 和跳跃表(SkipList)来保证数据的存储和有序
5.redis是如果进行存储数据的?
Redis是一种基于内存的Key-Value存储系统,它将所有的数据存储在内存中,因此具有非常快的读写速度。
6.说说mysql的三范式
- 第一范式:强调的是列的原子性,即数据库表的每列都是不可分割的原子数据项。
- 第二范式:非主键列完全依赖于主键,而不能是依赖于主键的一部分。
- 第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。
7.说说mysql是如何执行sql语句的
连接器:首先需要在 MySQL 客户端登陆才能使用,所以需要 连接器 来连接用户和 MySQL 数据库。
我们 一般是使用 mysql-u 用户名-p 密码来进行 MySQL 登陆,和服务端建立连接。在完成 TCP 握手后,连接器会根据你输入的用户名和密码验证你的登录身份。如果用户名或者密码错误,MySQL 就会提示 Access denied for user,来结束执行。如果登录成功后,MySQL 会根据权限表中的记录来判定你的权限。
查询缓存:
连接完成后,你就可以执行 SQL 语句了,这行逻辑就会来到第二步:查询缓存。MySQL 在得到一个执行请求后,会首先去查询缓存 中查找,是否执行过这条 SQL 语句,之前执行过的语句以及结果会以 key-value 对的形式,被直接放在内存中。key 是查询语句,value 是查询的结果。如果通过 key 能够查找到这条 SQL 语句,就直接妾返回 SQL 的执行结果。如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果就会被放入查询缓存中。可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,效率会很高。
执行sql解析器:
如果没有命中查询,就开始执行真正的 SQL 语句。首先,MySQL 会根据你写的 SQL 语句进行解析,分析器会先做词法分析,你写的 SQL 就是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串是什么,代表什么。然后进行语法分析,根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL语句是否满足 MySQL 语法。如果 SQL 语句不正确,就会提示 You have an error in yourSQL syntax。
执行优化器:
经过分析器的词法分析和语法分析后,你这条 SQL 就合法了,MySQL 就知道你要做什么了。但是在执行前,还需要进行优化器的处理,优化器会判断你使用了哪种索引,使用了何种连接,优化器的作用就是确定效率最高的执行方案。
最后进行执行:
MySQL 通过分析器知道了你的 SQL 语句是否合法,你想要做什么操作,通过优化器知道了该怎么做效率最高,然后就进入了执行阶段,开始执行这条 SQL 语句在执行阶段,MySQL 首先会判断你有没有执行这条语句的权限,没有权限的话,就会返回没有权限的错误。如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。对于有索引的表,执行的逻辑也差不多。
8.讲一下springboot的执行流程
- 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 文件,获取并创建 SpringApplicationRunListener 对象
- 然后由 SpringApplicationRunListener 来发出 starting 消息
- 创建参数,并配置当前 SpringBoot 应用将要使用的 Environment
- 完成之后,依然由 SpringApplicationRunListener 来发出 environmentPrepared 消息
- 创建 ApplicationContext
- 初始化 ApplicationContext,并设置 Environment,加载相关配置等
- 由 SpringApplicationRunListener 来发出 contextPrepared 消息,告知Spring Boot 应用使用的 ApplicationContext 已准备OK
- 将各种 beans 装载入 ApplicationContext,继续由 SpringApplicationRunListener 来发出 contextLoaded 消息,告知 Spring Boot 应用使用的 ApplicationContext 已装填OK
- refresh ApplicationContext,完成IoC容器可用的最后一步
- 由 SpringApplicationRunListener 来发出 started 消息
- 调用callRunners(…)方法,让实现了ApplicationRunner和CommandLineRunner接口类的run 方法得以执行,用于在 Spring 应用上下文准备完毕后,执行一些额外操作。从而完成最终的程序的启动。
- 由 SpringApplicationRunListener 来发出 running 消息,告知程序已运行起来了。
9.SpringMvc的执行流程:
- 用户点击某个请求路径,发起一个HTTP request请求,该请求会被提交到Dispatcher Servlet(前端控制器);
- 由Dispatcher Servlet请求一个或多个Handler Mapping(处理器映射器),并返回一个执行链(Handler Execution Chain);
- Dispatcher Servlet将Handler Mapping返回的执行链中的Handler信息发送给Handler Adapter(处理器适配器);
- HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
- Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
- HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
- DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
- ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
- DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
- 视图负责将结果显示到浏览器(客户端)
10.解释 Spring 框架中 Bean 的生命周期
- Spring 容器 从 XML 文件中读取 bean 的定义,并实例化 bean。
- Spring 根据 bean 的定义填充所有的属性
- 如果 bean 实现 了 BeanNameAware 接口 , Spring 传递 bean 的 ID 到setBeanName 方法。
- 如果 Bean 实现 了 BeanFactoryAware 接口 , Spring 传递 beanfactory 给setBeanFactory 方法。
- 如果有任何与 bean 相关联的 BeanPostProcessors , Spring 会 postProcesserBeforeInitialization()方法内调用它们。
- 如果 bean 实现 IntializingBean 了,调用它的 afterPropertySet 方法,如果 bean 声明了初始化方法,调用此初始化方法
- 如果有 BeanPostProcessors 和 bean 关联,这些 bean 的 postProcessAfterInitialization() 方法将被调用。
- 如果 bean 实现了 DisposableBean,它将调用 destroy()方法。
3月6号之后的面试
Java基础:
1.hash冲突的4种解决方案:
- 链地址法:对于相同的哈希值,使用链表进行连接。
- 再哈希法:提供多个哈希函数,如果第一个哈希函数计算出来的key的哈希值冲突了,则使用第二个哈希函数计算key的哈希值。
- 建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。
- 开发定址法:当关键字key的哈希地址p =H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,若p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。
2.为什么重写equals一定要重写hashcode方法?
在Java中,如果两个对象的equals方法返回true,则这两个对象的hashCode方法应该返回相同的值。
hashCode方法的作用是返回对象的哈希码,它被用于计算对象在哈希表中的存储位置。在使用哈希表进行查找时,首先会根据对象的哈希码找到对应的桶,然后再在该桶中查找对应的对象。如果两个对象的equals方法返回true,但它们的hashCode方法返回不同的值,那么就会导致哈希表无法正确地查找对象,从而影响程序的正确性和性能。所以在重写equals方法时必须重写hashcode,以保证对象的正确性和一致性。
3.冒泡排序需要多少个循环实现,循环了多少次呢?
冒泡排序需要2个for循环实现,循环了n+(n-1)+(n-2)+…+3+2+1次
4.String中有哪些常见的方法:
indexOf():获取字符串对象中第一次出现的索引
substring():从start开始截取字符串
equals():比较字符串的内容是否相同
equalsIgnoreCase():比较字符串对象内容是否相同,忽略大小写
startsWith():判断字符串对象是否以指定字符开头
endsWith():判断字符串对象是否以指定的字符结尾
isEmpty():判断指定字符串是否为空
toCharArray():把字符串转换为字符数组
toLowerCase():把字符串转换为小写字符串
toUpperCase():把字符串转换为大写字符串
split():去除字符串中指定的的字符,然后返回一个新的字符串
replace():将指定字符替换成另一个指定的字符
charAt():获取指定索引处的字符
length():获取字符串的长度,其实也就是字符个数
5.ArrayList和LinkList的区别?
- ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构
- ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
- LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。
6.说一下关于链表?
首先,链表分为单链表和双链表,单链表的每一个节点由保存的元素和一个前驱节点组成,如果前驱节点为null,那么这个节点为头节点。如果是双向链表,每个节点都有一个数据元,一个前驱节点,一个后继节点,如果一个节点的前驱节点为null,那么这个节点就是头节点,如果一个节点的后继节点为null,那么这个节点就是尾节点。
7.Map的使用场景
Map是一种常用的数据结构,用于存储键值对。它的主要作用是将一组键映射到一组值上,使得通过键可以快速地查找对应的值。Map的使用场景如下:
- 数据缓存:Map可以用于缓存一些需要反复访问的数据,例如Web应用中的Session、页面缓存等。
- 数据查询:Map可以用于快速地查找数据,例如通过ID来查找对象等。
- 数据统计:Map可以用于对某些数据进行统计,例如统计某个单词在文本中出现的次数。
- 数据分组:Map可以用于将数据按照某个属性进行分组,例如按照城市分组统计销售额等。
- 数据存储:Map可以用于存储一些需要按照键值对进行存储的数据,例如配置文件等。
I/O面试题
1.关于I/O,了解哪些?
NIO/AIO/BIO
BIO:同步阻塞I/O模型,也就是我们常用的I/O,特点是模式简单使用方便,数据的读取写入必须阻塞在一个线程内等待其完成,并发能力低。
NIO:是一种同步非阻塞I/O模型。是传统I/O的升级,它提供了Channel、Selector、Buffer,是支持面向缓冲的,实现了I/O的多路复用。
AIO:是对NIO的升级,实现了异步非阻塞式IO,异步IO的操作是基于事件和回调机制。
2.I/O多路复用
什么是多路复用?
多路线程复用是指一个进程/线程维护多个socket,它的实现方式有3种:
select:客户端操作服务器时会产生3种文件描述符(fd文件):select监控fd,有数据、可读、可写、出现异常,或超时时就会返回;再把fd拷贝回用户态中,然后通过遍历整个数组找到就绪的socket,进行对应的IO操作。
poll:原理和select一致,区别是poll使用链表的方式存储fd文件。
epoll:是在内核里使用红黑树来跟踪,增删查的时间复杂度是O(logn),把需要监控的socket通过epoll_ctl()函数加入到红黑树中,这样就不用传入所有的socket了,减少了从内核态到用户态的拷贝。
mysql面试题
1.说一下jdbc连接数据库的流程?
- 注册数据库驱动
- 获取数据库连接
- 获取传输器
- 传输sql获取结果
- 处理结果集 获取数据
- 关闭资源
2.什么是sql注入?
就是攻击者把 SQL 命令插入到 Web 表单的输入域或页面请求的查询字符串,欺骗服务器执行恶意的 SQL 命令
3.myisam和innodb的区别
MyISAM是mysql5之前的默认存储引擎,不支持事务,也不支持外键,使用的是表级锁,适合于那些以查询为主要操作的应用;
InnoDB是MySQL的5之后的存储引擎,它支持事务和外键,使用的是行级锁,适合于那些有高并发、高可靠性需求的应用。
4.mysql中常见的查询类型有哪几种?
- 普通查询:select 要查询的字段 from 表名
- 去重查询(distinct):select distinct 要查询的字段 from 表名
- 排序查询(order by): 升序:asc 降序:desc,一搬不加desc默认为升序排列
- 分组查询(group by):select xxx from 表名 group by xxx
- 等值查询
- 外连接查询:左外连接(左表过滤的结果全在),右外查询(右表过滤的结果全在)
- 全外连接查询
5.mysql索引为什么需要遵循最左前缀原则?
MySQL索引需要遵循最左前缀原则的原因是因为,在使用索引进行查询时,MySQL只能利用索引中的最左前缀来执行查询。如果查询条件没有使用索引的最左前缀,那么MySQL就无法使用索引进行查询,而需要进行全表扫描,效率会大大降低。
6.关于索引,你了解哪些?
索引是MySQL中用于提高查询性能的一种数据结构,它可以加速数据库的查询操作。索引是基于某个或多个列的值来创建的,可以大大减少查询操作需要扫描的数据量。
在MySQL中,常见的索引包括B-Tree索引、哈希索引、全文索引等等。其中,B-Tree索引是最常见的一种索引类型,它可以用于加速等值查询、区间查询和排序操作。
B-Tree索引是一种基于平衡树的索引结构,它将索引数据存储在一棵B-Tree上。B-Tree索引的主要特点是:
在创建索引时,需要注意以下几点:
- 索引列应该选择唯一性较高的列,例如主键、唯一索引等。
- 对于频繁更新的列,不宜创建索引,因为索引需要维护,会降低写入性能。
- 对于长文本字段,不宜创建索引,因为索引需要占用更多的存储空间。
- 在多列查询时,可以使用联合索引,它可以加速多列查询的效率。
- 要定期分析和优化索引,避免出现不必要的索引。、
7.mysql中有几种锁?
三种锁:
- 表级锁:加锁快,消耗小,不支持高并发,发生锁冲突的概率最高
- 行级锁:加锁慢,消耗大,支持高并发,发生冲突的概率最低,会发生死锁,可以解决脏读的问题。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
8.mysql为什么使用B+树而不是使用B树?
主要原因是:B+树在范围查找和遍历时更加高效。因为与B树不同,B+树只在叶子节点存储数据,而非叶子节点只存储索引信息,这样可以减少非叶子节点的存储空间,使得更多的叶子节点能够存储数据。同时,由于叶子节点之间构成了双向链表,因此在进行范围查找或遍历时,只需要遍历叶子节点就可以,而不需要像B树那样跳转到非叶子节点,这样就可以提高查询效率,减少IO次数。
9.慢sql如何定位并解决?
可以通过 show variables like ‘long_query_time’来查看具体是哪一条sql造成了慢查询。
首先用EXPLAIN查询盖语句索引是否生效,如果未生效,可能是全表扫描,导致效率很低;
大部分的慢sql可以通过加索引,或者分库分表的方式进行解决。
索引又可以分为聚簇索引和非聚簇索引,聚簇索引是值把索引和数据放到一起进行存储,非聚簇索引就是将数据和索引分开存储,索引结构的叶子节点指向了对应行。
10.什么是脏读?
是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。
11.还有哪些情况?
不可重复读:
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻读:
是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。
12.left join 和 join 的区别?
join只返回A表和B表的匹配行,而left join 返回A表中的所有行,B表中没有的数据,用null去补全。右连接同理。
13.Mysql事务,锁和Mvcc
你是怎么理解事务的?
首先:事务是可以是一组操作,要么全部成功,要么全部失败,事务的目的是为了保证数据的一致性。
事务的4大特征是:
就是ACID,分别是原子性,一致性,隔离性,持久性。
原子性是指当前事务要么成功,要么失败。原子性由undo log日志来保证,因为undo log记录着之前的值。如果出现异常,通过undo log记录的数据进行恢复。
隔离性是指当事务并发时,他们内部操作不能互相干扰。如果多个事务同时操作一个数据,就会出现脏读,重复读,幻读的问题。然后就有四种隔离级别:分别是读未提交,读已提交,可重复读,串行。
读未提交:就是读到未提交数据,产生脏读。如果读也加锁,就会降低性能,所以有了MVCC的解决方案,其中读已提交就是在读取的时候生成一个版本号,等到提交了事务,就会读取到最新的版本号的数据。
读已提交:
可重复读:当事务被修改,仍然会读取到修改前的数据,存在幻读的问题。MVCC解决了这个问题,用版本来控制。
串行:
持久性是指提交了事务之后,它对数据库的改变是永久的,会将数据持久化在硬盘上,持久化由redo log日志来保证。即使mysql挂掉了,也可以通过redo log来进行数据的恢复。
一致性是指我们使用事务的目的,需要通过应用程序的代码来保证。
在innoDB中,分为行锁和表锁,行锁是作用在索引上的,如果命中了索引,那么就是行锁,如果没命中索引,就是表锁,行锁分为读锁和写锁,读锁是共享锁,多个事务可以读取同一个资源,但是不允许其他事务修改,写锁是会阻塞其他的写锁和读锁。
14.B+树的层数为什么层数较少?
- B+树的每个节点可以存储多个关键字和指向子节点的指针,这个节点的阶数就是它可以存储的关键字的数量,通常阶数越大,树高度越低。
- 使用磁盘预读:B+树通常是存储在磁盘上的,为了提高磁盘访问效率,B+树采用了磁盘预读的策略。磁盘预读是指在读取一个磁盘块时,顺便把相邻的几个磁盘块也读取到内存中,这样可以避免多次磁盘访问。
- B+树的叶子节点存储的是数据,而非指向数据的指针,这样可以使得每个节点存储更多的数据,从而减少树的层数。
mybatis面试题
1.什么是mybatis?
Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关
注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂
的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
2.mybatis中#{}和${}有什么区别?
mybatis在处理#{}时,会将sql语句中的#{}替换为?号,调用PreParedStatement的set方法来赋值。
mybatis在处理${}时,会将${}替换为变量的值。使用#{}可以有效防止sql注入
3.MyBatis是如何进行1对多或者多对多查询的?(不是数据库的1对多或者多对多)
- 可以使用嵌套查询:可以在一个查询中包含另一个查询。在Mybatis中,可以通过主查询中使用ResultMap的association标签或者collection标签来实现嵌套查询。
- 延迟加载:使用延迟加载,可以在需求使用关联对象时再进行查询。在MyBatis中,可以通过设置fetchType属性为lazy来实现延迟加载。当使用延迟加载时,MyBatis会在需要使用关联对象时再去执行查询语句,而不是在主查询时就执行查询语句。
4.说一下MyBatis的缓存机制?
MyBatis 的缓存机制是指将查询结果缓存在内存中,以提高 SQL 执行效率的机制。MyBatis 的缓存机制可以分为一级缓存和二级缓存两种。
一级缓存
一级缓存是 MyBatis 默认开启的缓存机制,它是基于 SqlSession 的缓存,即在同一个 SqlSession 中,如果执行了相同的 SQL 语句,那么第二次执行时会从缓存中获取结果,而不会再次发送 SQL 到数据库。一级缓存的生命周期与 SqlSession 相同,也就是在 SqlSession 关闭之前都有效。
二级缓存
二级缓存是指将查询结果缓存到一个独立的缓存区域中,以便于多次 SqlSession 共享缓存,提高查询效率。二级缓存需要手动开启,并且需要在 MyBatis 配置文件中进行配置。开启二级缓存后,每次查询都会先从缓存中获取结果,如果缓存中不存在结果,则会去数据库中查询,并将查询结果缓存到缓存区域中。二级缓存的生命周期与整个应用程序相同,即在应用程序重启前都有效。
如何开启二级缓存:
在 MyBatis 的 XML 配置文件中,添加
<settings>
标签,并设置cacheEnabled
属性为true
,如下所示:<configuration> <settings> <setting name="cacheEnabled" value="true" /> </settings> ... </configuration>
对于需要启用二级缓存的 Mapper 文件,在对应的 XML 文件中添加
<cache>
标签,如下所示:<mapper namespace="com.example.MyMapper"> <cache/> ... </mapper>
5.mybatis是如何实现分页的?
MyBatis 实现分页的方式是通过在 SQL 语句中添加分页参数,根据这些参数计算分页的起始位置和结束位置,从而实现分页查询。
具体步骤是:
- 在 SQL 语句中添加分页参数,例如在 MySQL 中使用 LIMIT 和 OFFSET 关键字,Oracle 中使用 ROWNUM 关键字,SQL Server 中使用 TOP 和 ROW_NUMBER 函数等。
- 在 MyBatis 中配置分页插件,例如使用 PageHelper 插件,该插件可以自动生成分页 SQL 语句,并且支持多种数据库的分页方式。
- 在 Java 代码中调用分页查询方法,例如使用 PageHelper.startPage 方法设置分页参数,调用查询方法,最后获取分页结果。
6.mybatis中的延时加载是什么呢?
MyBatis中的延迟加载指的是在查询时不立即加载关联的对象,而是在使用时再进行加载。延迟加载可以提高查询性能和减少数据库的压力。
Redis面试题:
1.redis的持久缓存机制是什么?
Redis 提供两种持久化机制 RDB 和 AOF 机制:
RDB(Redis DataBase)持久化方式:
是指用数据集快照的方式(半持久化模式)记录 redis 数据库的所有键值对,在某个时间点将数据
写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点是快速,紧凑;缺点是可能会丢失最近的一部分数据。
AOF(Append-only file)持久化方式:
是指所有的命令行记录以 redis 命令请求协议的格式(完全持久化存储)保存为 aof 文件。
优点是数据更加可靠,缺点是相对于RDB更加耗时和占用更多的磁盘空间。
2.redis的主从复制原理?
redis全量复制一般发生在Slave阶段,此时Slave需要将Master上的所有的数据都复制一份:
首先从服务器连接主服务器,发送SYNC命令;
主服务器接收到命令后,开始生成RDB文件并使用缓冲区记录之后的所有的写命令;
主服务器执行完之后,向所有服务器发送快照文件,并在发送期间继续记录执行的写命令;
从服务器收到快照文件之后丢弃所有的旧数据,载入快照;
主服务器快照发送完毕之后开始向从服务器发送缓冲区中的写命令;
从服务器完成快照的载入,开始接收请求,并执行主服务器缓存区的写命令。
3.save和bgsave的区别?
在进行持久化时,Redis 提供了两个命令:SAVE 和 BGSAVE,它们的区别如下:
- SAVE 命令会阻塞 Redis 服务器,直到 RDB 文件创建完成为止,期间 Redis 无法处理任何命令请求。这意味着在 SAVE 执行期间,Redis 无法响应客户端的请求,可能会导致客户端出现超时或连接中断等问题。
- BGSAVE 命令是异步执行的,它会在后台创建 RDB 文件,期间 Redis 仍然可以响应客户端的请求。BGSAVE 命令会fork出一个子进程来执行持久化操作,父进程继续处理客户端请求。由于 BGSAVE 命令是异步执行的,所以它不会阻塞 Redis 服务器,不会影响 Redis 的响应速度。
Jvm面试题:
1.JVM如何判断对象是否可以被回收
要分辨一个对象是否可以被回收,有两种方式:引用计数法和可达性算法。
引用计数法
就是在对象被引用时,计数加1,引用断开时,计数减1。那么一个对象的引用计数为0时,说明这个对象可以被清除。
这个算法的问题在于,如果A对象引用B的同时,B对象也引用A,即循环引用,那么虽然双方的引用计数都不为0,但如果仅仅被对方引用实际上没有存在的价值,应该被GC掉。
可达性算法
通过引用计数法的缺陷可以看出,从被引用一方去判定其是否应该被清理过于片面,所以我们可以通过相反的方向去定位对象的存活价值:一个存活对象引用的所有对象都是不应该被清除的(Java中软引用或弱引用在GC时有不同判定表现,不在此深究)。这些查找起点被称为
GC Root
哪些对象可作为GC Root呢?
2.JVM的类加载机制有哪几种?
Java虚拟机(JVM)的类加载机制包括以下三种:
- 启动类加载器(Bootstrap ClassLoader):它是JVM内置的类加载器,用于加载JVM运行时需要的基础类,如Java核心类库(java.*)等。它是用C++实现的,无法通过Java代码访问。
- 扩展类加载器(Extension ClassLoader):它负责加载Java扩展类库(javax.*)等,通常位于JDK安装目录下的jre/lib/ext子目录中。
- 应用程序类加载器(Application ClassLoader):它是应用程序的默认类加载器,用于加载应用程序自己的类。它通常从CLASSPATH环境变量指定的路径中加载类。在大多数情况下,它也是用户自定义类的最终加载器。
计算机网络面试题
1.线程与进程的区别?
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。一个程序至少有一个进程,一个进程至少有一个线程。
2.当我们打开百度,输入,Helloword点击回车,会发生什么?
TCP协议的3次握手:
第一次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;
第二次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;
第三次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束;
TCP协议的4次挥手:
第1次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态;
第2次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入CLOSE_WAIT状态;
第3次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态;
第4次挥手:客户端收到FIN后,客户端t进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,服务端进入CLOSED状态,完成四次挥手。
3.Https的加密过程
HTTPS(HyperText Transfer Protocol Secure)是HTTP协议的安全版,它通过使用SSL(Secure Sockets Layer)或TLS(Transport Layer Security)协议来保护Web通信的安全性。
HTTPS的加密过程如下:
- 客户端发起HTTPS请求:客户端向服务器发起HTTPS请求,请求连接建立。
- 服务端发送数字证书:服务端将自己的数字证书发送给客户端,包括服务端的公钥、证书颁发机构、证书有效期等信息。数字证书通常是由第三方机构颁发的,用来证明服务端的身份。
- 客户端验证数字证书:客户端收到服务端的数字证书后,会对证书进行验证,包括证书的合法性、证书颁发机构的可信度、证书是否过期等。
- 客户端生成随机密钥:如果数字证书验证通过,客户端会生成一个随机密钥,用于加密数据。
- 客户端使用公钥加密密钥:客户端使用服务端的公钥对随机密钥进行加密,并将加密后的密钥发送给服务端。
- 服务端使用私钥解密密钥:服务端收到客户端发送的加密密钥后,使用自己的私钥对密钥进行解密。
- 数据加密传输:客户端和服务端使用该随机密钥对数据进行加密传输,保证数据的安全性。
4.TCP和UDP的区别
TCP和UDP有如下区别:
- TCP是面向连接的协议,UDP是无连接的协议
- TCP是可靠的协议,UDP是不可靠的
- UDP比TCP更快
- TCP是面向字节流的协议
- TCP适用于要求数据传输安全、可靠、有序的应用场景。UDP适用于要求实时性、速度优先的应用场景,如音视频、游戏等。
设计模式面试题:
5.说一下单例模式的代码实现?
单例模式是防止一个对象被多次加载和创建,来造成不必要的资源消耗,他们都是线程安全的。
单例模式分为饿汉式和懒汉式
- 饿汉式单例,通过虚拟机只加载一次class对象来实现线程安全
- 懒汉式单例,在进行创建时,会优先进行判断是否为空,若不为空就使用,为空则创建后使用。
6.除了单例模式,还了解其他的设计模式嘛?
- 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪个类;
- 观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象;
- 适配器模式:将一个类的接口转换成客户希望的另一个接口;
- 策略模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换;
- 装饰器模式:动态地给一个对象添加一些额外的职责;
Spring面试题
1.说说你对Spring的理解?
Spring主要是IOC和AOP,IOC是控制反转的思想,用依赖注入去是实现,把对象交给容器去管理。
AOP是面向切面编程的意思,就是把在不改变代码逻辑的情况下,把公共的行为封装为一个模块,对代码进行增强。
AOP通过动态代理实现:
动态代理又分为jdk动态代理和cgLib动态代理
jdk动态代理有限制,必须实现一个或者多个接口,cgLib不需要实现接口。
2.动态代理和静态代理的区别是?
静态代理在程序编译时就会生成一个class文件,动态代理是在编译时无法生成class文件,在运行时才能生成class文件,动态代理通过反射获取类来生成class文件。
3.说一下Spring的底层结构?
Spring 框架的底层结构主要包括以下几个部分:
- 核心容器:包括 BeanFactory 和 ApplicationContext,是 Spring 框架的核心部分,提供了依赖注入(DI)和控制反转(IOC)等功能。
- AOP框架:Spring 提供了 AOP 框架,可以在不修改源代码的情况下,实现横切关注点的功能,如事务处理、日志记录、性能监控等。
- 数据访问:Spring 提供了一系列的数据访问技术,包括 JDBC、ORM、事务等。Spring 的 JDBC 模块提供了 JDBC 技术的封装,简化了数据库操作。Spring 的 ORM 模块支持多种 ORM 框架,如 Hibernate、MyBatis 等。Spring 的事务模块提供了事务管理的支持,可以在不同的事务管理器之间进行切换。
- Web 框架:Spring 提供了一系列的 Web 框架,包括 Spring MVC、Spring WebFlux、Spring Web Services 等,可以用于构建 Web 应用程序。
- 测试框架:Spring 提供了一系列的测试框架,如 Spring Test、Spring Boot Test 等,可以帮助开发人员编写单元测试、集成测试等。
- 其他模块:Spring 还提供了一些其他的模块,如 Spring Security、Spring Integration、Spring Batch 等,可以用于实现安全性、集成性、批处理等功能。
4.Spring事务传播行为
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES NEW:创建一个新的事务,如果当前线程存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,就把当前事务挂起。
5.Spring使用事务注解为什么会失效?
使用事务注解事务到底在什么时候提交呢?该方法没有抛出异常的情况下就会自动提交事务。
我们在代码当中做了try操作 没有将异常往外抛,所以 事务注解对应的aop就没有拦截,认为方法是没有异常的则直接提交事务。
解决方法:需要在我们的catch中手动回滚事务。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
6.Spring中事务的默认隔离级别是?
Spring事务的默认隔离级别是Isolation.READ_COMMITTED(读已提交)。这个隔离级别表示一个事务只能读取到已经提交的数据,避免了脏读现象的发生。在该级别下,一个事务只有在另一个事务提交后才能读取到其所做的修改。
7.Spring是如何解决循环依赖的?
Spring通过三级缓存机制解决循环依赖问题:
首先是spring内部的三级缓存:
第一级缓存:singletonObjects,用于保存实例化、注入、初始化完成的bean实例;
第二级缓存:earlySingletonObjects,用于保存实例化完成的bean实例;
第三级缓存:singletonFactories,用于保存bean创建工厂,以便后面有机会创建代理对象。
当Spring在创建Bean时发现循环依赖时,它会将正在创建的Bean放入第一级缓存中,然后立即返回一个ObjectFactory,这个ObjectFactory会在Bean创建完成后返回Bean实例,以解决循环依赖问题。
然后Spring会继续创建Bean,并将正在创建的Bean放入第二级缓存中。当Bean创建完成后,Spring会将其属性注入,并将其放入第三级缓存中,然后再从第三级缓存中取出Bean,并返回给ObjectFactory。
这样,Spring就能够在循环依赖时保证Bean的正确创建和注入,同时还能避免循环依赖导致的死锁问题。但是,如果循环依赖的链条过长,或者存在多个构造器、多个Setter方法等复杂情况,仍然可能会导致Bean创建失败。因此,在使用Spring时还需注意避免复杂的循环依赖问题。
8.Spring,SpringMVC和SpringBoot三者的区别以及SpringBoot是如何对Spring进行简化的?
Spring,SpringMVC和Spring Boot是三个不同的框架,都是由Spring源项目派生而来。
- Spring是一个基础框架,提供了一系列的核心功能,如依赖注入、AOP(面向切面编程)等。Spring通过IOC容器来管理对象的生命周期,并提供了基于AOP的事务管理、安全控制等功能。
- SpringMVC是基于Spring的一个Web框架,提供了一系列的Web开发功能,如控制器、视图解析器、消息转换器等。它可以帮助开发者快速搭建基于MVC架构的Web应用程序。
- pring Boot是一个快速构建基于Spring的Web应用程序的框架。它通过自动配置、约定优于配置等方式简化了Spring应用程序的配置和部署。Spring Boot还提供了一些方便的功能,如内嵌式Web服务器、健康检查、度量等。
Spring Boot可以通过以下方式对Spring进行简化:
- 自动配置:Spring Boot通过自动配置来消除繁琐的配置。它基于类路径中存在的jar包和其他条件,为应用程序提供默认配置,从而大大减少了配置文件的编写。
- 内嵌式Web服务器:Spring Boot提供了内嵌式Web服务器,如Tomcat、Jetty和Undertow,无需将应用程序部署到外部Web服务器上即可运行应用程序。
- Starter依赖:Spring Boot提供了一组预定义的Starter依赖,这些依赖包含了常用的依赖库和配置,如Spring MVC、JPA、Thymeleaf等,可以大大简化项目的依赖管理和配置。
- Actuator:Spring Boot提供了一个Actuator模块,可以用来监控和管理Spring Boot应用程序。Actuator提供了健康检查、度量、配置信息等功能,可以帮助开发者更好地管理和监控应用程序。
- 约定优于配置:Spring Boot采用约定优于配置的方式,使用一些默认的约定来配置应用程序。这样,开发者不需要显式地指定每一个配置细节,从而减少了配置的复杂性。
JUC面试题
1.对象的组成:
对象的组成有3个部分:对象头;实例数据;对齐填充字节。其中对象头包含3个部分:Mark Word;指向类的指针;数组长度(如果当前对象不是数组则没有此部分)
2.Synchronized锁升级原理:
在锁对象的对象头里面,有一个threadId字段,在第一次访问的时候,threadId为空,jvm让其有偏向锁,把threadId设置为线程id,再次进入就会先判断threadId和线程id是否一致,如果一致,就直接使用此对象,如果不一致,则升级偏向锁为轻量锁,通过自旋循环来获取锁,执行一定次数后,要是还获取不到锁,就升级轻量锁为重量锁。
3.ConcurrentHashMap在JDK1.7和JDK1.8中的区别?
在JDK1.7中,ConcurrentHashMap使用分段锁实现并发控制,即将整个Map分成多个Segment,每个Segment拥有自己的锁,只对该Segment进行加锁,不会影响其他Segment的并发性能。但是,在高并发情况下,由于Segment的数量是固定的,可能会导致某些Segment被频繁访问而出现热点的问题,从而影响整个Map的并发性能。
而在JDK1.8中,ConcurrentHashMap的实现方式进行了重构,使用了CAS操作和synchronized来实现并发控制。相比于分段锁,在高并发情况下,JDK1.8中的ConcurrentHashMap可以更好地实现负载均衡,减少热点问题的发生,从而提高整个Map的并发性能。此外,JDK1.8中还添加了一些新的API,如forEach()和reduce()方法,方便对Map进行遍历和聚合操作。
4.ConcurrentHashMap的底层实现原理?
ConcurrentHashMap在jdk1.8中是由数组,单向链表和红黑树构成的,当我们初始化一个ConcurrentHashMap实例的时候,默认会初始化一个长度为16的数组,由于核心依然是Hash表,所以依然会存在Hash冲突的问题,所以用链式寻址的方式来解决Hash冲突的问题,当Hash冲突比较多的时候,会造成链表长度较长的问题,这种情况下会导致Map中的数组元素查询复杂度增加。
其功能和HashMap是一样的,ConcurrentHashMap在HashMap的基础上提供了一个并发安全的一个实现,主要是通过对Node节点加锁来保证数据更新的安全性
5.线程池中提交一个任务的流程是怎么样的?
- 在使用execute()方法提交一个Runnable对象时
- 会先判断当前线程池中的线程数是否小于corePoolSize
- 如果小于,则创建新线程并执行Runnable
- 如果大于等于,则尝试将Runnable加入到workQueue中
- 如果workQueue没满,则将Runnable正常入队,等待执行
- 如果workQueue满了,则会入队失败,那么会尝试继续增加线程
- 如果当前线程池中的线程数是否小于maximumPoolSize
- 如果小于,则创建新线程并执行任务
- 如果大于等于,则执行拒绝策略,拒绝此Runnable
6.Synchronized锁的底层原理:
Synchronized底层是通过monitorenter和monitorexit指令进行的,其内部有一个计数器,当计数器为0时,表示可以获取锁,执行monitorenter之后,计数器+1,执行monitorexit之后,计数器-1,释放锁。当一个线程进来之后,发现计数器不为0,则会阻塞。
7.Synchronized为什么锁升级?
因为Synchronized是重量级锁,加锁开锁的开销比较大,所以需要对锁进行升级,有轻量级锁,偏向锁,重量级锁。
8.Synchronized和Lock锁有什么区别?
Synchronized可以给类,方法,代码块进行加锁;Lock只能给代码块加锁;
Synchronized不需要手动获取锁和释放锁,发生异常会自动释放锁,不会造成死锁问题。Lock需要自己加锁和释放锁。如果使用不当unLock()就会发生死锁问题。
通过Lock可以知道是否成功加锁,Synchronized无法知道。
9.Synchronized和Volatile的区别?
Synchronized关键字用于实现线程之间的同步,可以将代码块或方法声明为同步方法,以保证在同一时刻只有一个线程可以访问共享资源。
Volatile关键字用于实现多个线程之间的变量可见性,当一个线程修改了一个被Volatile修饰的变量时,其他线程可以立即看到这个变量的最新值。
10.CAS锁了解嘛?
CAS是自旋锁的意思,没有获取到锁的时候不会阻塞,而是循环一直不断得获取锁。
CAS有3个操作数:内存值,旧的预期值,新的值,当旧的值和内存值相同时,就会把内存值修改为新的值,,否则什么都不会改变。
11.如何配置线程池参数?
cpu密集型:核心线程数 = cpu数量+1
IO密集型:核心线程数 = 2 * cpu
分布式面试
1.说说分布式事务的理解和解决方案
分布式事务是指事务的参与者,支持事务的服务器,资源服务器以及事务管理器分别位与分布式系统的不同节点之上。
分布式事务是为了不同数据库的数据一致性,基于CAP定理,我们要么采用强一致性方案,要么采用弱一致性方案。
而强一致性方案是指通过第三方事务管理器来协调多个节点的事务性,保证每一个节点的事务达到同时成功或者同时失败,为了实现这样一个需求,会引入X/Open DTP模型提供的XA协议基于2阶段提交或者3阶段提交的方式去实现,但是如果全局事务管理器中的多个节点任意一个节点在进行事务提交确认的时候,由于网络通信延迟导致了阻塞就会影响到所有节点的事务提交,而这个阻塞的过程也会影响到用户的请求线程,这对于用户体验以及整体的性能影响比较大。
而弱一致性的方案就是针对强一致性方案所衍出来的性能和数据一致性平衡的一个方案,就是损失掉强一致性,数据在某一个时刻会存在不一致的状态,但是最终这些数据会达成一致,这样的好处就是提升了系统的性能,在弱一致性方案中,常见的解决方案有第一个使用分布式消息队列来实现最终的一致性,第二个是基于TCC事务通过演讲版本的第二阶段提交实现最终一致性,第三个是使用Seata事务框架,它提供了多种事务模型,比如AT,XA,Saga,TCC等不同的模型,提供的是强一致性或者弱一致性的支持。
2.JSON是一个怎样的对象?
JSON是一种轻量级的数据交换格式。它是基于JS对象字面量的语法,但可以被用于多种编程语言中。
是一种KV的键值对的集合。
3.分布式的特性
cap 一致性,可用性,分区容错性。
4.布隆过滤器如何删除过滤的值?
布隆过滤器是一种空间效率高、误判率低的数据结构,它可以快速地判断一个元素是否存在于集合中。布隆过滤器本质上是一个位向量,通过多个哈希函数对元素进行映射,将元素映射到位向量上的多个位置上,并将这些位置的值设置为 1。
由于布隆过滤器的特殊性质,它不支持删除元素。这是因为在布隆过滤器中,一个元素的多个哈希值可能被多个元素共用,如果直接删除某个元素的位向量上的值,可能会影响到其他元素的判断结果。因此,如果需要删除某个元素,需要重新构建一个新的布隆过滤器。
一种解决方案是使用 Counting Bloom Filter(计数布隆过滤器),它在布隆过滤器的基础上增加了计数器,用于记录某个元素被添加的次数。当需要删除某个元素时,可以将对应的计数器减一,如果计数器为 0,则可以将该元素从过滤器中删除。但是,这种方法会增加存储空间和计算复杂度。
另一种解决方案是使用基于时间的布隆过滤器(Time-Based Bloom Filter),它可以在布隆过滤器的基础上,添加时间戳信息,记录元素被添加的时间。当需要删除某个元素时,可以通过时间戳信息判断该元素是否已经过期,如果已经过期,则可以将该元素从过滤器中删除。但是,这种方法需要增加时间戳信息的存储空间,并且需要定期清理过期的元素,否则可能会影响过滤器的误判率。