java后端面试题总结(持续更新)

(一) java基础

1.封装、继承、多态

封装:封装是指将对象的属性私有化,对外提供一些公共的方法来访问这些私有属性。


继承:如果一个类继承了另一个类,这个类就叫做子类,另一个类就叫父类,那么子类就拥有了父类的所有属性和方法,子类也可以有自己特有的属性和方法。

 
多态:比如说一个接口可以有不同的实现类,我们可以用接口来声明变量,用具体实现类来创建对象赋给这个变量,因为编译看左边,运行看右边,所以,在编译阶段还不清楚我们具体使用是哪个类,等到运行的时候才能真正确认这个类。

2、String、StringBuilder 、 StringBuffer 的区别

String 跟其他两个类的区别是:

String是final类型,每次声明的都是不可变的对象,是字符串常量。

所以每次操作都会产生新的String对象,然后将指针指向新的String对象。

StringBuffer,StringBuilder都是在原有对象上进行操作

所以,如果需要经常改变字符串内容,则建议采用这两者。

StringBuffer 和 StringBuilder:

StringBuilder是线程不安全的,但是性能比较好。

StringBuffer是线程安全的,但是性能差一些。

在开发中,优先采用StringBuilder.

StringBuilder > StringBuffer > String

3、接口和抽象类的区别是什么?

①一个类可以同时实现多个接口,但是不能同时继承多个抽象类

②Java8之前的版本,接口中的方法只有方法的声明,不能有方法体,抽象中的方法可以有方法体

③接口中的方法修饰符默认是public,所以修饰符可以省略不写

④接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。

4、关于 final 关键字的一些总结

final主要用于变量、方法和类中

1.变量中:被final修饰的基本数据类型变量必须被显式地初始化,且初始化以后不能被改变;被final修饰的引用变量在初始化以后不能指向别的对象。

2.方法中:被final修饰的方法无法被其子类重写;同时能够在一定程度上提升方法执行效率。类中所有的private方法被隐式定义为了final方法

3.类中:被final修饰的类无法被继承,类中的所有成员方法都默认修饰final

5、什么是java序列化?如何实现java序列化?

java序列化就是将对象转换成字节序列。实现方法就是让对应对象实现serialization接口,是一个标记性接口,不需要重写方法,然后构造一个ObjectOutPutStream对象输出流,使用writeObject方法将Obj写成字节序列

6、说说常见的集合有哪些?

Collection、List、Set、Map

其中Collection是List和Set的父接口,所以后两者可以使用Collection中的方法。

List集合:存取元素有放入顺序,元素可以重复。

主要实现类有:ArrayList和LinkedList

ArrayList底层数据结构是数组,查询快,增删慢

LinkedList底层数据结构是双向链表,查询慢,增删快

Set集合:存取元素无放入顺序,元素不可以重复

主要实现类有HashSet、LinkedHashSet、TreeSet

HashSet:底层数据结构是哈希表,无序,元素唯一

        如何来保证元素唯一性?
        依赖两个方法:hashCode()和equals()

        LinkedHashSet:底层数据结构是链表和哈希表,有序、唯一

        链表保证有序,哈希表保证唯一

        TreeSet:底层数据结构是红黑树,有序、唯一

Map集合:存取元素时按键值对存储,无放入顺序,唯一

        HashMap: 在 JDK 1.8 之前是由“数组+链表”组成,在JDK 1.8时,底层是由“数组+链表+红黑树”组成。

        数组+链表的结构:如下图

        图片来源:10分钟拿下 HashMap_一小页的博客-CSDN博客

by zhanghaolin

        其中:链表的节点存储的是一个 Entry 对象,每个Entry 对象存储四个属性(hash,key,value,next)

        三句话,说清它的数据结构:
        1.整体是一个数组;
        2.数组每个位置是一个链表;
        3.链表每个节点中的Value即我们存储的Object;

        数组+红黑树的结构:如下图

        图片来源:面试阿里,HashMap 这一篇就够了_程序员囧辉的博客-CSDN博客

         什么时候用链表?什么时候用红黑树?

        对于插入,默认情况下是使用链表节点。当同一个索引位置的节点在新增后达到9个(阈值8):如果此时数组长度大于等于 64,则会触发链表节点转红黑树节点(treeifyBin);而如果数组长度小于64,则不会触发链表转红黑树,而是会进行扩容,因为此时的数据量还比较小。

        对于移除,当同一个索引位置的节点在移除后达到 6 个,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点(untreeify)。

7、== 与 equals的区别

        ==:

        如果==比较的是基本数据类型,那就是比较他们的值是否相等。

        如果==比较是的引用数据类型,就是比较他们的地址是否相等。

        equals():

        比较两个对象是否相等。如果没有重写equals,作用和==类似。如果重写了equals,一般重写都是比较内容了

        拿String来说:

        String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。

        当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。

8、方法的重写和重载的区别

        重载:发生在一个类中,方法名相同,参数列表不同.(注意和返回类型是没有关系的)如果方法名相同,参数列表相同,返回值类型不同,则直接报错,因为会认为是统一方法.

        重写:发生在父类与子类之间,方法名相同,参数列表相同.

9、谈谈throw和throws的区别

        throw,作用于方法内,用于主动抛出异常

        throws, 作用于方法声明上,声明该方法有可能会抛些某些异常

10、什么是线程池?为什么要用线程池?

        线程池:就是事先创建多个可执行的线程放入一个容器中,需要的时候从容器中获取而不用自行创建。线程使用完毕时不需要销毁线程而是放回容器中,从而减少创建和销毁线程对象的开销。

        作用:使用线程池可以避免频繁地创建和销毁线程。
                   使用线程池可以根据项目灵活地控制并发的数目。

11、并发与并行的区别

        并发:

        多个事件在同一时间段内发生

        同一个CPU执行多个任务,按细分的时间片交替执行

        并发的多个任务会相互抢占资源

        并行:

        多个事件在同一时间 点上发生

        在多个CPU上同时处理多个任务

        并行的多个任务不会相互抢占资源

12、Java中创建多线程有几种方法?

  1. 继承Thread类,重新run方法,由于类单一继承的特性,不能再继承其他类,拓展性弱。
  2. 实现Runnable接口并重写run方法,可以继续继承其他类,拓展性强
  3. 实现Callable接口并重写call方法,相比较前两种,可以获取到线程执行结果的返回值。

13、如何启动一个线程

        线程对象是调用start方法来启动线程而不是run方法。

(二)数据库

1、事务的概念

        事务是一组操作的集合,这些操作要么同时成功,要么同时失败。

2、事务的特性

        原子性:事务可以被视为一组不可分割的最小工作单位,在一个事务种的所有操作要么同时成功,要么同时失败。

        一致性:在事务的执行前后,数据会保持一致性,就是说总数据不会增加也不应该减少,类似能量守恒定律。

        隔离性:主要体现在并发的环境下,不同事务之间互不干扰,一个事务执行的中间状态不会被另一个事务所看见。
        持久性:事务执行后,对数据的修改会永久地保存在数据库种。

3、事务的隔离级别

        ①事务可能出现的并发问题

        在实际应用中,数据库中的数据是要被多个用户共同访问的,当不同用户访问同一个数据时,就有可能出现如下的并发问题:

        脏读:一个事务读取到了另一个事务还没有提交的数据叫做脏读

        不可重复读:在一个事务内,先后读取同一行数据两次,但是得到的结果不一样。

        幻读:在一个事务内,先后执行两次查询操作,但是第二次查询的时候出现了第一次查询没有的数据。

        ②为了应对上述并发问题,就出现了以下四种隔离级别

        Read uncommitted (读未提交):最低级别,以上三种问题都无法解决。

        Read committed (读已提交):可避免脏读情况出现

        Repeatable Read(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。

        Serializable (串行化):最高隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不可重复读、幻读情况的发生。但这个级别可能导致大量的超时现象和锁竞争,性能较差,在实际应用中很少使用。

4、索引

        索引是用来快速查找具有特点值的记录,如果没有创建索引的话,在执行查询操作的时候会遍历整张表,这样查询效率就会很低。

        索引的原理就是把无序的数据变成有序,在数据库中,如果根据某列创建了索引,就会将表中的很多条记录按照列进行排序。可以用字典来理解,索引相当于字典的目录,在执行查询的时候,不需要遍历全部内容,而是可以根据索引快速定位到某条记录。只不过在数据库中,查找这项工作是mysql帮我们完成的,我们只需要提供查询信息,就是创建索引。

5、聚集索引和二级索引的区别(聚簇索引和非聚簇索引)

        底层数据结构都是B+树

        聚集索引:数据和索引放在一起,索引结构、也就是B+树的叶子节点存放的就是行数据,即真正的数据

        二级索引:数据和索引分开,索引结构的叶子几点保存的是相对应的主键

6、mysql锁的类型有哪些?

        基于锁的属性分类:共享锁、排他锁。

        共享锁:又称为读锁,如果一个事务需要进行查询操作,那将会给数据加上读锁,为数据加了读锁之后,其他事务只能再对该数据加读锁,不能加写锁。直到所有的读锁释放之后,其他事务才能再加写锁。共享锁的特性是为了并发地读取数据,但是读数据的时候不允许数据被修改,可避免不可重复读的问题。

        排他锁:又称为写锁,如果一个事务需要进行修改操作,那将会给数据加上写锁,给数据上了写锁后,那不允许其他事务对该数据加任何锁,直到写锁释放,其他事务才能再加锁。写锁的目的就是指,在对数据进行修改的时候,不允许其他人同时修改,也不允许其他人读取,可以避免脏读的问题。

        基于锁的粒度分类:行级锁、表级锁、页级锁、记录锁、间隙锁、临键锁。

        表级锁:锁住的是整张表。

        行级锁:锁住的是一行或多行记录。

        记录锁:锁住的是一条记录。

        页锁:介于行级锁和表级锁的一种锁,锁住的是相邻的一组记录。

        间隙锁:是行锁的一种,锁住的是表记录的一个区间,区间是指相邻的记录之间出现的空隙,比如id为1的记录和id为4的记录之间没有记录,这就产生了空隙。遵循左开右闭的原则,即id为4的也会锁住。

        临键锁:是记录锁和间隙锁的组合。

        基于锁的状态分类:意向共享锁、意向排他锁。

        意向锁:举个例子,事务A给一张表中的一行加上了读锁,那其他事务只能加读锁读取数据,不能再加写锁修改数据。问题就来了,此时事务B想给这整张表加上写锁,如果此时事务B能加锁成功,理论上B就能修改表中的每一行数据,显然就产生矛盾了。

        如何解决这个问题呢?

        可以这样做,事务A在给表中的一行加读锁,加锁成功之后,就给整张表追加上一个提示:这张表里的一行我已经加了读锁,其他人不准再对整张表加写锁了。

        如果不加上这个提示,又该怎样做呢,事务B就得逐行去判断表中的行是否已经加上了读锁,这样显然很麻烦。

        这个提示就是意向锁,意向锁属于表锁。

        根据事务A加上的锁的类型:共享锁还是排他锁,又分为意向共享锁和意向排他锁。

        意向共享锁:如果表上存在意向共享锁,不允许其他事务再对表加排他锁。

        意向排他锁:如果表上存在意向排他锁,不允许其他事务再对表加排他锁以及共享锁。

        不过,意向锁之间不会互斥。

7、redis是什么?

        Redis(Remote Dictionary Server) 是一个开源的、高性能非关系型(NoSQL)的键值对数据库。(NoSQL的意思是not only sql)

        Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。

        与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。

(三)框架

1、Spring是什么?

        Spring是一个开源的轻量级Java开发框架,目的是用于简化企业应用程序的开发。

2、如何理解ioc(控制反转)

        控制反转ioc:英文是Inversion of Control,翻译过来就是控制反转的意思。控制反转简单理解就是说,当我们需要用到某个对象时,不再需要自己手动通过new指令去创建对象,而是把创建对象等工作交给spring框架来做,将对象的控制权交给spring容器,由spring容器来管理对象的生命周期(比如创建和销毁的工作)以及对象之间的依赖关系。其实这样也大大降低了对象之间的耦合度。

        依赖注入DI:

        依赖注入是控制反转的具体实现。

        依赖:比如说一个对象想要实现某种功能需要借助或者说依赖于另一个对象中的方法,这就是依赖。按传统的方式做便是在这个类的定义里通过new指令来创建出另一个对象,然后调用其方法,但是spring已经帮我们创建好了对象,我们不需要再自己创建了。

        注入:注入就是赋值的意思,就是指从spring容器中取出对象赋值给我们自己声明的变量。有两种方式实现,第一种是通过xml文件,第二种是通过注解的方式来完成。

3、如何理解aop(面向切面编程)

        AOP为Aspect Oriented Programming,即面向切面编程。比如说我们如果想给业务方法增加一些非业务性功能,如记录日志、计算业务方法执行的时间等。按传统的方式来做的话,我们就得在每一个业务方法前后加上同样的代码来实现,这样代码重复度就会很高而且冗余。aop就是将这些可重用的非业务性功能代码抽取并封装成一个模块,这个模块就叫做切面。

        aop技术静态代理实现的代表是AspectJ,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。就是说当我们使用了aop技术后,在程序编译阶段会生成相应的代理对象,而这个代理对象就是已经在原来业务方法的基础上增加了非业务性功能的对象,由这个代理对象代替原来的旧对象去执行。这样做便减少了系统中的重复代码,降低了模块间的耦合度,提高了系统的可维护性

4、Springmvc内部的执行流程

        ①用户通过浏览器将请求发送给前端控制器dispatcherServlet

        ②前端控制器dispatcherServler将把请求交给处理器映射器handleMapping

        ③处理器映射器handleMapping会根据收到的请求找到相应的处理器和拦截器对象,生成处理器执行链,并将其发给前端控制器dispatcherServlet

        ④前端控制器再将处理器执行链中的控制器对象交给相应的处理器适配器handleAdapter,然后处理器适配器调用控制器对象controller,并调用执行控制器对象中的方法,得到modelAndView对象,将modelAndView发给前端控制器

        ⑤前端控制器将modelAndView对象交给视图解析器ViewResolve,视图解析器将封装好的view对象返回给前端控制器

        ⑥DispatcherServlet 调用视图对象view,让其自己 (View) 进行渲染(将模型数据填充至视图中),生成响应对象 (HttpResponse)

        ⑦前端控制器 (DispatcherServlet) 把HttpResponse对象响应给浏览器,展示在页面上。

5、@Autowired和@Resource之间的区别

        @Autowired:是spring框架提供的注解,默认是按照类型注入。

        @Resource:是java提供的注解,默认是按照指定名称注入,其次才是按照类型注入

6、springboot优势

        (1)简化配置,不需要编写太多的xml配置文件
        (2)大大简化了spring开发
        (3)SpringBoot可以创建独立运行的应用而不需要依赖于容器
        (4)内置tomcat服务器,不需要打包成war包,可以直接放到tomcat中运行
        (6)为微服务SpringCloud奠定了基础,使得微服务的构建变得简单
        (7)Spring可以整合很多各式各样的框架,并能很好的集成

7、 Mybatis中 #{} 和 ${}的区别

        #{}是预编译,是占位符,可以有效地防止SQL注入。

        ${}字符串替换,是拼接符,直接拼接在SQL语句中。

        另外说明:

        mybatis在处理#{}时,会将其替换成问号:?,调用preparedStatement来赋值

        mybatis在处理${}时,会将其替换成变量中的值,调用statement来赋值

8、session和cookie的区别

session和cookie的区别 - 前端—小白 - 博客园

9、synchronized关键字详解

synchronized关键字详解_加油进大厂的博客-CSDN博客_synchronized关键字

10、 多线程面试题集合

java多线程 —— 面试题集合(最全集合)_Charles Ren的博客-CSDN博客_多线程面试题

11、 线程的 sleep() 方法与 wait() 方法的区别

总的来说,线程的 sleep() 方法和 wait() 方法有以下几点区别:

(1)sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法。

(2)两者的主要区别在于sleep() 方法不会释放锁,但是wait() 方法会释放锁

(3)sleep() 方法是指将线程暂停一段时间,等到了指定的时间后会重新恢复运行,但是wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法来唤醒它。

12、RestController和Controller的区别

百度安全验证

13、SpringBoot中有哪些常用注解 

SpringBoot面试题:SpringBoot中有哪些常用注解_JTraveler的博客-CSDN博客_实际开发中要用到springboot中的哪些注解

14、20道经典Redis面试题

20道经典Redis面试题_CSDN砖家的博客-CSDN博客_redis面试题

你可能感兴趣的:(java,面试,spring,mysql)