1.可重复读,已提交读,这两个隔离级别表现的现象是什么,区别是什么样的?
可重复读:表示整个事务看到的事务和开启后的事务能看到的数据是一致的,既然数据是一致的,所以不存在不可重复读。而且不会读取其他事务修改的数据,也就是不存在脏读。而对同一个批数据,可能会存在添加的情况,所以可能会存在幻读的情况。
读已提交:该隔离级别只能读取到其他事务提交后的数据,所以不存在脏读(是指当一个事务正在访问数据并对数据进行了修改,而这种修改还没有提交到数据库中时,另一个事务也访问了这个数据并使用了这个数据。因为这些数据是未提交的,所以另一个事务读到的数据是“脏数据”,基于这些数据所做的操作可能是不正确的。)。但是在第一次读取数据后,其他事务修改后数据并提交事务,此时事务读取到数据就和第一次读到的数据不一致了,也就存在不可重复读。同时其他事务可以添加多条数据,也存在幻读(是指当事务不是独立执行时发生的现象,例如第一个事务对一个表中的数据进行修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。)。
2.数据管理里,数据文件大体分成哪几种数据文件?
例如:我们创建student数据库,然后会在 /var/lib/mysql/ 目录里面创建一个以 student 为名的目录,然后保存表结构(student.frm)和表数据(student.ibd)的文件都会存放在这个目录里。
3.日志文件分成了哪几种?
-
redo log 重做日志,是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复;
-
undo log 回滚日志,是 Innodb 存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和 MVCC。
-
bin log 二进制日志,是 Server 层生成的日志,主要用于数据备份和主从复制;
-
relay log 中继日志,用于主从复制场景下,slave通过io线程拷贝master的bin log后本地生成的日志
-
慢查询日志,用于记录执行时间过长的sql,需要设置阈值后手动开启
log4j的日志级别?
从高到低依次是OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、ALL
ALL 最低等级的,用于打开所有日志记录。
TRACE很低的日志级别,一般不会使用
ERROR指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。
WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。
OFF 最高等级的,用于关闭所有日志记录。
如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。
从我们实验的结果可以看出,log4j默认的优先级为ERROR或者WARN(实际上是ERROR)。
4. 说下MVCC机制的原理?
MVCC就是多版本并发控制,实现了读写的并发控制,解决了幻读的问题(一个数据前后两次读取到的数据不一致,在RR(可重复读)的事务隔离级别下,Innodb采用了一个MVCC的机制和LBCC去解决幻读问题,MVCC类似于一种乐观锁的设计,简单来说就是针对每个事务生成一个事务版本,然后通过undo的版本链进行管理,并且在MVCC里面规定了高版本能够看到低版本的事务变更,低版本看不到高版本的事务变更,从而去实现不同版本之间的事务隔离,解决了幻读的问题)。
5.索引的类型有哈希索引,B+树索引,而hash索引的时间复杂度是o1,那为什么我们一般情况下不使用哈希索引,而使用b+树索引呢?
哈希索引的key是经过hash运算得出的,即跟实际数据的值没有关系,因此哈希索引不适用于范围查询和排序操作;容易导致全表扫描,因为可能存在不同的key经过hash运算后值相同;索引列上的值相同的话,易造成hash冲突,效率低下。
MySQL的索引为什么使用B+树而不使用跳表
B+树是多叉平衡搜索树,扇出高,只需要3层左右就能存放2kw左右的数据,同样情况下跳表则需要24层左右,假设层高对应磁盘IO,那么B+树的读性能会比跳表要好,因此mysql选了B+树做索引。redis的读写全在内存里进行操作,不涉及磁盘IO,同时跳表实现简单,相比B+树、AVL树、少了旋转树结构的开销,因此redis使用跳表来实现ZSET,而不是树结构。
B树和B+树的区别?
1.关键字的数量不同;B+树中分支结点有m个关键字,其叶子结点也有m个,其关键字只是起到了一个索引的作用,但是B树虽然也有m个子结点,但是其只拥有m-1个关键字。
2.存储的位置不同;B+树中的数据都存储在叶子结点上,也就是其所有叶子结点的数据组合起来就是完整的数据,但是B树的数据存储在每一个结点中,并不仅仅存储在叶子结点上。
3.分支结点的构造不同;B+树的分支结点仅仅存储着关键字信息和儿子的指针(这里的指针指的是磁盘块的偏移量),也就是说内部结点仅仅包含着索引信息。
4.查询不同;B树在找到具体的数值以后,则结束,而B+树则需要通过索引找到叶子结点中的数据才结束,也就是说B+树的搜索过程中走了一条从根结点到叶子结点的路径。
6. 对一个慢sql怎么去排查?
可通过开启mysql的慢日志查询,设置好时间阈值,进行捕获慢 sql;针对慢 sql,进行 explian 去查看执行计划。
7.索引字段是不是建的越多越好?
不是,建的的越多会占用越多的空间,而且在写入频繁的场景下,对于B+树的维护所付出的性能消耗也会越大。
8. http协议的报文的格式有了解吗?
分为请求报文和响应报文。
请求报文:
-
请求行:包含请求方法、请求目标(URL或URI)和HTTP协议版本。
-
请求头部:包含关于请求的附加信息,如Host、User-Agent、Content-Type等。
-
空行:请求头部和请求体之间用空行分隔。
-
请求体:可选,包含请求的数据,通常用于POST请求等需要传输数据的情况。
响应报文:
-
状态行:包含HTTP协议版本、状态码和状态信息。
-
响应头部:包含关于响应的附加信息,如Content-Type、Content-Length等。
-
空行:响应头部和响应体之间用空行分隔。
-
响应体:包含响应的数据,通常是服务器返回的HTML、JSON等内容。
9.http常用的状态码?
常见的具体状态码有:200:请求成功;301:永久重定向;302:临时重定向;404:无法找到此页面;405:请求的方法类型不支持;500:服务器内部出错。
10.MyBatis运用了哪些常见的设计模式?
-
建造者模式(Builder),如:SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder等;
-
工厂模式,如:SqlSessionFactory、ObjectFactory、MapperProxyFactory;
-
单例模式,例如ErrorContext和LogFactory;
-
代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;
-
模板方法模式,例如BaseExecutor和SimpleExecutor,例如IntegerTypeHandler;
-
适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;
11. MyBatis中创建了一个Mapper接口,在写一个xml文件,java的接口是要实现的,为什么这没有实现呢?
因为执行Sql所需要的所有的JDBC操作都在Mybatis的MapperProxy中实现了,所以不需要实现类。
12.与传统的JDBC相比,MyBatis的优点?
-
基于 SQL 语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于统一管理;提供 XML 标签,支持编写动态 SQL 语句,并可重用。
-
与 JDBC 相比,减少了 50%以上的代码量,消除了 JDBC 大量冗余的代码,不 需要手动开关连接;
-
很好的与各种数据库兼容,因为 MyBatis 使用 JDBC 来连接数据库,所以只要 JDBC 支持的数据库 MyBatis 都支持。
-
能够与 Spring 很好的集成,开发效率高
-
提供映射标签,支持对象与数据库的 ORM 字段关系映射;提供对象关系映射 标签,支持对象关系组件维护。
13. JDBC连接数据库的步骤?
-
加载数据库驱动程序:在使用JDBC连接数据库之前,需要加载相应的数据库驱动程序。可以通过 Class.forName("com.mysql.jdbc.Driver") 来加载MySQL数据库的驱动程序。不同数据库的驱动类名会有所不同。
-
建立数据库连接:使用 DriverManager 类的 getConnection(url, username, password) 方法来连接数据库,其中url是数据库的连接字符串(包括数据库类型、主机、端口等)、username是数据库用户名,password是密码。
-
创建 Statement 对象:通过 Connection 对象的 createStatement() 方法创建一个 Statement 对象,用于执行 SQL 查询或更新操作。
-
执行 SQL 查询或更新操作:使用 Statement 对象的 executeQuery(sql) 方法来执行 SELECT 查询操作,或者使用 executeUpdate(sql) 方法来执行 INSERT、UPDATE 或 DELETE 操作。
-
处理查询结果:如果是 SELECT 查询操作,通过 ResultSet 对象来处理查询结果。可以使用 ResultSet 的 next() 方法遍历查询结果集,然后通过 getXXX() 方法获取各个字段的值。
-
关闭连接:在完成数据库操作后,需要逐级关闭数据库连接相关对象,即先关闭 ResultSet,再关闭 Statement,最后关闭 Connection。
import java.sql.*;
public class Main {
public static void main(String[] args) {
try {
// 加载数据库驱动程序
Class.forName("com.mysql.cj.jdbc.Driver");
// 建立数据库连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
// 创建 Statement 对象
Statement statement = connection.createStatement();
// 执行 SQL 查询
ResultSet resultSet = statement.executeQuery("SELECT * FROM mytable");
// 处理查询结果
while (resultSet.next()) {
// 处理每一行数据
}
// 关闭资源
resultSet.close();
statement.close();
connection.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
14.怎么理解SpringIoc(解耦)
IOC:控制反转 其本身是一个容器,在之前的编码过程中,我们需要什么对象去创建什么对象,由程序员自己来控制对象,有了IOC之后,IOC容器来控制对象,spring提供了一种方式,这种方式就是spring提供一个容器,我们在xml文件里定义各个对象的依赖关系,由容器完成对象的构建,当我们java代码里需要使用某个实例的时候就可以从容器里获取,那么对象的构建操作就被spring容器接管,所以它被称为控制反转,控制反转的意思就是本来属于java程序里构建对象的功能交由容器接管,依赖注入就是当程序要使用某个对象时候,容器会把它注入到程序里,这就叫做依赖注入。
IOC有三种注入方式︰构造器注入、setter方法注入、根据注解注入
15.如果让你设计一个SpringIoc,你觉得会从哪些方面考虑这个设计?
-
Bean的生命周期管理:需要设计Bean的创建、初始化、销毁等生命周期管理机制,可以考虑使用工厂模式和单例模式来实现。
-
依赖注入:需要实现依赖注入的功能,包括属性注入、构造函数注入、方法注入等,可以考虑使用反射机制和XML配置文件来实现。
-
Bean的作用域:需要支持多种Bean作用域,比如单例、原型、会话、请求等,可以考虑使用Map来存储不同作用域的Bean实例。
-
AOP功能的支持:需要支持AOP功能,可以考虑使用动态代理机制和切面编程来实现。
-
异常处理:需要考虑异常处理机制,包括Bean创建异常、依赖注入异常等,可以考虑使用try-catch机制来处理异常。
-
配置文件加载:需要支持从不同的配置文件中加载Bean的相关信息,可以考虑使用XML、注解或者Java配置类来实现。
16. Spring给我们提供了很多扩展点,这些有了解吗?
-
BeanFactoryPostProcessor:允许在Spring容器实例化bean之前修改bean的定义。常用于修改bean属性或改变bean的作用域。
-
BeanPostProcessor:可以在bean实例化、配置以及初始化之后对其进行额外处理。常用于代理bean、修改bean属性等。
-
PropertySource:用于定义不同的属性源,如文件、数据库等,以便在Spring应用中使用。
-
ImportSelector和ImportBeanDefinitionRegistrar:用于根据条件动态注册bean定义,实现配置类的模块化。
-
Spring MVC中的HandlerInterceptor:用于拦截处理请求,可以在请求处理前、处理中和处理后执行特定逻辑。
-
Spring MVC中的ControllerAdvice:用于全局处理控制器的异常、数据绑定和数据校验。
-
Spring Boot的自动配置:通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置。
-
自定义注解:创建自定义注解,用于实现特定功能或约定,如权限控制、日志记录等。
17. 大致了解SpringMVC的处理流程吗?
-
用户发送请求至前端控制器DispatcherServlet
-
DispatcherServlet收到请求调用处理器映射器HandlerMapping。
-
处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
-
DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,如:参数封装,数据格式转换,数据验证等操作
-
Handler处理器执行相应的业务逻辑,生成ModelAndView。
-
HandlerAdapter将处理器的执行结果包装成ModelAndView并返回到DispatcherServlet。
-
DispatcherServlet将ModelAndView传给ViewReslover视图解析器
-
视图解析器根据ModelAndView找到对应的视图进行渲染。
-
将渲染后的视图返回给客户端。
18. Handlermapping 和 handleradapter有了解吗?
HandlerMapping负责将请求映射到处理器(Controller)。根据请求的URL、请求参数等信息,找到处理请求的 Controller。
HandlerAdapter负责调用处理器(Controller)来处理请求。处理器(Controller)可能有不同的接口类型(Controller接口、HttpRequestHandler接口等),HandlerAdapter根据处理器的类型来选择合适的方法来调用处理器。
19. SpringAOP主要想解决什么问题
AOP(是IOC的一个扩展功能,先有的IOC,再有的AOP,只是在IOC的整个流程中新增的一个扩展点而已),一般称为面向切面,为解耦而生,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect) ,减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理。任何一个系统都是由不同的组件组成的,每个组件负责-块特定的功能,当然会存在很多组件是跟业务无关的,例如日志事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到且标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动本代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
SpringAOP的原理了解吗
Spring AOP的实现依赖于动态代理技术。动态代理是在运行时动态生成代理对象,而不是在编译时。它允许开发者在运行时指定要代理的接口和行为,从而实现在不修改源码的情况下增强方法的功能。Spring AOP支持两种动态代理:
-
基于JDK的动态代理:使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现。这种方式需要代理的类实现一个或多个接口。
-
基于CGLIB的动态代理:当被代理的类没有实现接口时,Spring会使用CGLIB库生成一个被代理类的子类作为代理。CGLIB(Code Generation Library)是一个第三方代码生成库,通过继承方式实现代理。
动态代理和静态代理的区别
代理是一种常用的设计模式,目的是:为其他对象提供一个代理以控制对某个对象的访问,将两个类的关系解耦。代理类和委托类都要实现相同的接口,因为代理真正调用的是委托类的方法。区别:
-
静态代理:由程序员创建或者是由特定工具创建,在代码编译时就确定了被代理的类是一个静态代理。静态代理通常只代理一个类;
-
动态代理:在代码运行期间,运用反射机制动态创建生成。动态代理代理的是一个接口下的多个实现类。
代理模式和适配器模式有什么区别?
-
目的不同:代理模式主要关注控制对对象的访问,而适配器模式则用于接口转换,使不兼容的类能够一起工作。
-
结构不同:代理模式一般包含抽象主题、真实主题和代理三个角色,适配器模式包含目标接口、适配器和被适配者三个角色。
-
应用场景不同:代理模式常用于添加额外功能或控制对对象的访问,适配器模式常用于让不兼容的接口协同工作。
20.java线程的生命周期有了解吗?
新建(New):当线程对象对创建后,它只是进入了新建状态,此时它已经有了内存空间和其他资源,但线程还没开始执行。
就绪(Runnable):当调用线程对象的start()方法,线程即进入就绪状态。处于这个状态的线程位于可运行线程池中,等待获取CPU的使用权。
运行(Running):当CPU开始调度处于就绪状态的线程,使得线程进入运行状态。此时,它才真正开始执行run()方法里面的代码。
阻塞(Blocked):一个正在运行的线程如果调用了某些方法,如等待输入/输出完成,或者等待获取某个同步锁,则线程会进入阻塞状态,并且释放CPU使用权。
等待(Waiting):一个正在运行的线程可以调用obj.wait()方法,使得当前线程进入等待状态。进入这个状态后,线程会释放掉它所持有的所有资源,而且不会去争夺锁。
终止(Terminated):线程会在以下几种情况下进入终止状态:
21.使用多线程要注意哪些问题?
要保证多线程的允许是安全,不要出现数据竞争造成的数据混乱的问题。
Java的线程安全在三个方面体现:
-
原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作,在Java中使用了atomic和synchronized这两个关键字来确保原子性;
-
可见性:一个线程对主内存的修改可以及时地被其他线程看到,在Java中使用了synchronized和volatile这两个关键字确保可见性;
-
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序,在Java中使用了happens-before原则来确保有序性。
22. 保证数据的一致性有哪些方案呢?
-
事务管理:使用数据库事务来确保一组数据库操作要么全部成功提交,要么全部失败回滚。通过ACID(原子性、一致性、隔离性、持久性)属性,数据库事务可以保证数据的一致性。
-
锁机制:使用锁来实现对共享资源的互斥访问。在 Java 中,可以使用 synchronized 关键字、ReentrantLock 或其他锁机制来控制并发访问,从而避免并发操作导致数据不一致。
-
版本控制:通过乐观锁的方式,在更新数据时记录数据的版本信息,从而避免同时对同一数据进行修改,进而保证数据的一致性。
23. 线程池有了解吗?线程池大概的原理?
线程池是为了减少频繁的创建线程和销毁线程带来的性能损耗。
线程池分为核心线程池,线程池的最大容量,还有等待任务的队列,提交一个任务,如果核心线程没有满,就创建一个线程,如果满了,就是会加入等待队列,如果等待队列满了,就会增加线程,如果达到最大线程数量,如果都达到最大线程数量,就会按照一些丢弃的策略进行处理。
-
corePoolSize:线程池核心线程数量。默认情况下,线程池中线程的数量如果 <= corePoolSize,那么即使这些线程处于空闲状态,那也不会被销毁。
-
maximumPoolSize:线程池中最多可容纳的线程数量。当一个新任务交给线程池,如果此时线程池中有空闲的线程,就会直接执行,如果没有空闲的线程且当前线程池的线程数量小于corePoolSize,就会创建新的线程来执行任务,否则就会将该任务加入到阻塞队列中,如果阻塞队列满了,就会创建一个新线程,从阻塞队列头部取出一个任务来执行,并将新任务加入到阻塞队列末尾。如果当前线程池中线程的数量等于maximumPoolSize,就不会创建新线程,就会去执行拒绝策略。
-
keepAliveTime:当线程池中线程的数量大于corePoolSize,并且某个线程的空闲时间超过了keepAliveTime,那么这个线程就会被销毁。
-
unit:就是keepAliveTime时间的单位。
-
workQueue:工作队列。当没有空闲的线程执行新任务时,该任务就会被放入工作队列中,等待执行。
-
threadFactory:线程工厂。可以用来给线程取名字等等
-
handler:拒绝策略。当一个新任务交给线程池,如果此时线程池中有空闲的线程,就会直接执行,如果没有空闲的线程,就会将该任务加入到阻塞队列中,如果阻塞队列满了,就会创建一个新线程,从阻塞队列头部取出一个任务来执行,并将新任务加入到阻塞队列末尾。如果当前线程池中线程的数量等于maximumPoolSize,就不会创建新线程,就会去执行拒绝策略。
24.ArrayList和LinkedList有什么区别
-
底层数据结构:ArrayList使用数组作为底层数据结构,而LinkedList使用双向链表作为底层数据结构
-
随机访问性能:ArrayList支持通过索引直接访问元素,因为底层数组的连续存储特性,所以时间复杂度为O(1)。而LinkedList需要从头或尾部开始遍历链表,时间复杂度为O(n)。
-
插入和删除操作:ArrayList在尾部插入和删除元素的时间复杂度为O(1),因为它只需要调整数组的长度即可。但在中间或头部插入和删除元素时,需要将后续元素进行移动,时间复杂度为O(n)。而LinkedList在任意位置插入和删除元素的时间复杂度为O(1),因为只需要调整节点的指针即可。
-
内存占用:ArrayList在每个元素中都存储了实际的数据,而LinkedList在每个节点中存储了数据和前后节点的指针。因此,相同数量的元素情况下,LinkedList通常比ArrayList占用更多的内存空间。
25. ArrayList线程安全吗?把ArrayList变成线程安全有哪些方法?
不是线程安全的,ArrayList变成线程安全的方式有:
26.对面向对象的理解?
封装:将对象属性和方法的代码封装到一个模块中,也就是一个类中,保证软件内部具有优良的模块性的基础,实现“高内聚,低耦合”。
继承:在已经存在的类的基础上进行,将其定义的内容作为自己的内容,并可以加入新的内容或者修改原来的方法适合特殊的需要。
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,就是多态,简单点说:就是用父类的引用指向子类的对象。目的:提高代码复用性,解决项目中紧耦合问题,提高可扩展性。
-
读书中遇到最难的技术是什么,怎么克服的?
-
有没有什么强项在面试中还没有展现的?