Java面试

  1. 根据招聘岗位的要求梳理一份技能清单
  2. 根据技能清单写好最终的简历
  3. 最后再按照简历的要求去学习和提升。

重点总结

  1. Java 基础、集合、并发、MySQL、Redis 、Spring、Spring Boot 这些 Java 后端开发必备的知识点(MySQL + Redis >= Java > Spring + Spring Boot)。
  2. JVM 相关的知识点。JVM 面试中比较常问的是 Java 内存区域JVM 垃圾回收类加载器和双亲委派模型 以及 JVM 调优和问题排查(我之前分享过一些常见的线上问题案例,里面就有 JVM 相关的)。一般是大厂(例如美团、阿里)和一些不错的中厂(例如携程、顺丰、招银网络)才会问到,面试国企、差一点的中厂和小厂就没必要准备了。
  3. 重点学习Java 基础、集合、并发,MySQL、Redis,Spring、Spring Boot

Java相关

  1. Java基础:13个
  2. Java集合框架:8个
  3. Java并发(进阶):25个
  4. JVM(进阶):29个
    总计75个

MySQL相关

  1. 存储引擎:2个
  2. 事务:4个
  3. 字段类型:4个
  4. 索引:8个
  5. :6个(表级锁、行级锁、行锁类型、Next-Key Lock、当前读与快照读、乐观锁/悲观锁)
  6. 日志(binlog/redo log等):7个
  7. 性能优化:10个(包括分库分表、读写分离等)
    总计41个

Redis相关

  1. 基础:4个
  2. 应用:4个(分布式锁、消息队列等)
  3. 数据结构:5个
  4. 持久化:2个
  5. 线程模型:3个
  6. 内存管理:3个(过期策略、内存淘汰、内存碎片)
  7. 事务:3个
  8. 性能优化:5个(批量操作、热Key、大Key等)
  9. 生产问题:5个(缓存穿透、雪崩、击穿等)
  10. 集群:14个(Sentinel、Cluster分片等)
    总计47个

Spring相关

涵盖Spring框架、Spring Boot相关问题:

  1. Spring基础:9个(IoC、AOP、Bean生命周期等)
  2. Spring Boot:5个(Starter、自动配置、嵌入容器等)
    总计14个

总结

  • Java:75个
  • MySQL:41个
  • Redis:47个
  • Spring:14个

Java基础

基础概念和常识

Java语言特点

  1. 简单易学(语法简单,上手容易)
  2. 面向对象(封装,继承,多态)
  3. 平台无关性(Java虚拟机实现平台无关性)
  4. 支持多线程
  5. 可靠性(具备异常处理和内存管理机制)
  6. 安全性(Java语言本身就提供了多重安全防护机制,如访问权限关键字,限制程序直接访问系统资源)
  7. 高效性
  8. 支持网络编程

Java SE 和 Java EE

Java SE(Java Platform,Standard Edition)Java平台标准版。包含了支持应用程序开发和运行的核心类库和虚拟机等核心组件。可以用于桌面应用程序开发或简单的服务器应用程序开发。

Java EE(Java Platform,Enterprise Edition)Java平台企业版。在Java SE的基础上,包含了支持企业应用程序开发和部署的标准和规范(如Servlet,JSP)。Java EE可以用于开发分布式,可移植,健壮,安全的服务的Java应用程序,如web应用程序

简单来说,Java EE可以用于开发复杂的企业应用程序或web应用程序,Java EE可以开发简单的桌面应用程序或服务器应用程序。

JVM VS JDK VS JRE

JVM(Java Virtual Machine)是运行Java字节码的虚拟机,JVM针对不同的系统(如windows,linux,macOS)都有不同的特定实现,目的是为了在不同的系统中运行相同的字节码都能得到同样的结果。

不同的编程语言(比如Java,Kotlin)都能通过各自的编译器编译成class字节码文件,最终通过JVM在不同的平台上运行。

JDK和JRE

JDK是Java开发工具包,供开发者使用,用于创建和编译Java程序。它包含了JRE,以及编译器javac和其他工具,如 javadoc(文档生成器)、jdb(调试器)、jconsole(监控工具)、javap(反编译工具)等。

JRE是运行java程序的环境,主要包含两个部分

  • JVM,用于运行Java字节码的虚拟机
  • Java基础类库(CLASS LIBRARY),用于提供常用的功能和API(如I/O操作,网络通信和数据结构等)

jdk-include-jre

字节码是什么?好处是什么

JVM可以理解的代码就是字节码(即扩展名为.class的文件),它不面向任何特定处理器,只面向JVM虚拟机。

它的好处是:

  • 解决了传统解释性语言执行效率低的问题
  • 保留了解释性语言可移植的特性

基本数据类型✨

Java 中的几种基本数据类型了解么?

6种数字类型:

    • 4种整型(byte,short,int,long)
    • 2种浮点型(float,double)
  • 一种字符类型:char
  • 一种布尔类型:boolean

byte,short,int,long,float,double,boolean,char字节分别为1,2,4,8,4,8,1,2,位数在字节数的基础上×8即可

其默认值分别为0,0,0,0l,0f,0d,false,'u0000'

对应的包装类型为Byte,Short,Integer,Long,Float,Double,Boolean,Character

基本类型和包装类型的区别?

  • 用途: 除了常量和局部变量之外,我们一般很少在方法参数或对象属性中使用基本类型来定义变量。并且包装类型支持泛型,而基本类型不支持
  • 存储方式:基本类型的局部变量一般存放于JVM虚拟机栈的局部变量表中,对于成员变量,非static修饰的一般存放于堆中,static修饰在jdk7及之前存放于方法区中,在jdk8开始存放于元空间中。而包装类型是对象类型,几乎所有对象实例都存放于堆中。
  • 占用空间:一般基本类型相较于对象类型的包装类型而言占用空间非常小
  • 默认值:包装类型不赋值就是null,基本类型都有其默认值而非null
  • 比较方式:基本类型的值一般使用进行比较,而包装类型使用比较的是其对象的内存地址,所以我们一般采用equals()方法进行比较

包装类型的缓存机制/常量池技术了解么?

基本类型的包装类型大部分都采用了缓存机制/常量池技术,用于提高性能

其中Byte,Short,Integer,long这4种包装类型默认创建了数值在[-128,127]的相应类型的缓存数据,Character创建了数值在[0.127]范围的缓存数据,Boolean默认返回True或False,float,double没有对应实现

为什么要有包装类型?

  • 在一些业务场景中,我们要求对象的属性不赋值就为null,而使用基本数据类型不赋值是有默认值的,这时候包装类型就显得必要。
  • 在需要使用泛型时,基本数据类型是没有泛型的,因此只能使用包装类型

自动装箱拆箱是什么?其原理是?

装箱:将基本数据类型使用其对应的引用类型进行包装

拆箱: 将包装类型转换为基本数据类型

装箱其实调用了包装类型的valueOf()方法,拆箱调用了其initValue()方法

注意:频繁的拆装箱可能导致严重的性能损耗,比如在循环中使用包装类型进行加减乘除

自动拆箱导致的NPE

在包装类型为Null时,将包装类型赋值给基本数据类型,会导致自动拆箱产生NPE异常,另外在三目运算符中也会导致

//表达式1或表达式2中只要一个是基本数据类型
//两个表达式类型不一致,会自动拆箱升级为范围更大的那个数据
Integer i = null;
Boolean flag = false;
System.out.println(flag?1:i)

浮点数运算时为什么会有精度丢失的风险?

计算机是二进制的,其表示的数字是有宽度的,当表示一个无限循环的小数时,就只能进行截断,这就会导致小数发生精度损失

如何解决浮点数运算精度丢失?

使用Bigdecimal,其可以实现浮点数的精确运算不会存在精度丢失的情况

超过Long整形的数据如何表示?

使用BigInteger,其使用int[]数组的形式保存任意大小的整形数据

注意其在进行加减乘除运算时,相对于常规整数类型效率会较低

面向对象基础

深拷贝和浅拷贝区别了解吗?什么是引用拷贝?

主要是在内部对象上有区别:

浅拷贝会创建一个新的对象,如果拷贝对象内部属性是一个引用类型的话,那么就会将内部对象的引用地址进行复制,也就是说,新对象和拷贝对象是共用一个内部对象

深拷贝会完全复制拷贝对象,并且也会复制拷贝对象的内部对象

引用对象就是两个不同的引用指向一个对象

内部类了解吗?匿名内部类了解吗

内部类是定义在一个类内部的类,它可以直接访问外部成员(包括私有成员),按照定义位置和方式的不同,又可以分为三种,分别是成员内部类,静态内部类和方法内部类

成员内部类的特点是可以直接外部成员,其必须先有外部类实例才能进行对象创建

静态内部类使用static进行修饰的内部类,它不能直接访问外部非静态成员,也不需要外部类实例就能进行对象创建

方法内部类是在类内部的方法中定义的类,他只能访问方法中final修饰的局部变量

匿名内部类是没有名字的类,它通常用于简化代码,尤其是在实现接口或继承类并重写其方法时,只需要用new关键字进行创建,也只能访问方法中final修饰的局部变量

Object

== 和 equals() 的区别

使用==时,基本数据类型和引用类型的作用效果是不同的

对于基本数据类型,==比较的是其值是否相等

对于引用数据类型,==比较的是其内存地址是否相等

对于equals()来说,基本数据类型是没有equals()方法的,其是用来比较两个对象是否相等,equals()方法存在于object类当中,因为object类是所有类的父类,所以所有类都包含equals()方法

还有就是,euqals()方法不重写的话其实是和==等价的,因此一般都会对equals()方法进行重写来比较两个对象的属性是否相等

hashCode() 有什么用?

hashCode()的作用在于获取哈希码,也称为散列码,它是一个int整数。这个哈希码的作用在于确定对象在哈希表中的索引位置。

为什么要有 hashCode?/hashCode 与 equals ?

hashCode()和equals()都用于判断两个对象是否相等。

hashCode()的作用在于帮助我们大大缩小了查找成本,比如在HashSet中,首先会为新加入的对象生成一个hashCode用于确定其在数据结构中的位置,如果hashCode与数据结构中其他的对象的hashcode都不相等的话那我们就认为没有对象重复出现,可以加入。如果hashcode相等的话,我们就会使用euqals()方法将其与哈希值相等的对象进行比较,如果相等,就会不让其加入,如果不相等,就会将其重新散列到其他位置

哈希碰撞问题会导致使用哈希算法生成hashCode时,多个对象也可能传回相同的哈希值。

String

String、StringBuffer、StringBuilder 的区别?

  • 可变性:string是不可变的,其原因是String中的字符数组属性采用的final修饰符进行修饰,而String对象也并没有向外暴露修改这个属性的方法,因此其不可变。StringBuffer和StringBuilder都有一个共同的抽象父类AbstractStringBuilder,其父类是通过字符数组保存数据的,不过没有采用final修饰符进行修饰,因此可变
  • 线程安全:string可以认为是常量,因此是线程安全的。StringBuffer对父类方法加了同步锁,因此也是多线程安全的。而StringBuilder并没有加锁,因此是不安全的。
  • 性能:String中的内容改变时,会创建一个新的String对象,并将对象引用指向新的对象。而StringBuffer只会对其对象本身进行操作,无需创建新对象并改变对象引用。StringBuilder只比StringBuffer性能提高了百分之十到百分之十五,却要面临多线程不安全的风险。

应用场景:

  • 操作少量数据使用String

  • 单线程操作大量数据使用StringBuilder

  • 多线程操作大量数据使用StringBuffer

String 为什么是不可变的?

首先,String类中是通过char数组来存储字符串,这个char数组是使用了final修饰符来修饰的,并且String类也没有前外暴露其修改的方法

其次,String类也通过了final修饰符来进行修饰,因此其也不能被其他类所继承,也无法修改其中的属性

String s1 = new String("abc"); 这段代码创建了⼏个字符串对象?

首先可以知道是,创建了一个或者两个对象

创建两个对象的情况是字符串常量池没有对于值为abc的String对象,因此首先会调用ldc指令在字符串常量池中创建一个abc对象,然后new String()又在堆中创建了一个String对象

创建一个对象的情况是字符串常量池中存在值为abc的String对象,因此只有new String会在堆中创建一个对象

异常

Exception 和 Error 有什么区别?

在java中,所有的异常都一个共同的Throwable父类,Throwable类下又有两个主要的子类:

第一个是Exception,也就是程序可以处理的异常,可以通过catch进行捕获,它又分为Checked Exception(受检查异常,必须处理)和Unchecked Exception(不受检查异常,可以不处理)

第二个就是Error,也就是程序不可以处理的错误,不建议使用catch进行捕获,因为这类异常比如虚拟机错误,内存溢出发生时,虚拟机会选择中断该线程

泛型

什么是泛型?有什么作用?

泛型是JDK5中引入的一个新特性,可以使用泛型参数,提高代码的可读性和稳定性。

编译器可以对泛型参数进行检查,如果传入的对象与泛型参数的对象不相同,那么编译器就会报错。

并且,原生的List返回Object对象,而使用泛型的话,编译器会将其自动转换为指定的对象

什么是类型擦除?

Java泛型是伪泛型,在编译期间,所有泛型信息都会被擦掉,这就是泛型擦除。

好处是:泛型擦除本质上是编译器的行为,在保证引入泛型机制的同时又不创建新的对象,这样可以减少虚拟机的开销

什么是通配符,有什么作用?

泛型类型是不可变的,为了增加其灵活性,就引入了通配符,通配符允许参数类型发生改变

其分为

  • 无界通配符<?>:可以指定任意类型
  • 上界通配符,<? extends T>,类型只能使用T及其子类,主要用于读取数据,不能写入数据
  • 下界通配符,<? super T>,类型只能使用T及其父类, 主要用于写入数据,也可以读取数据,但读取到的数据只能为Object类型

反射

Java 反射?

Java反射是在程序运行时允许获取类的信息并操作类的属性和方法的机制。通过反射,我们可以在程序运行时创建对象,修改属性和调用方法,而无需在编译时知道类的具体结构。

反射有几个核心类,分别是:

Class:表示一个类或接口

Field:表示类的成员变量

Method:表示类的方法

Constructor:表示类的构造方法

Java 反射有何缺点?

优点:

使我们的代码更灵活,能够在允许时分析类并操作类的属性或方法。

缺点:

首先是安全问题,比如使用反射会无视泛型参数在编译时的安全检查。

其次是性能问题,会增加虚拟机的性能开销

你是怎么理解反射的(为什么框架需要反射)?

比如在SpringBoot和Myatis中的动态代理就需要反射进行实现

其次就是Spring中的注解也使用了反射进行实现。

注解

何谓注解?

注解是java5引入的新特性,可以理解为特殊的注释,主要用于类,变量或者方法,通过注解我们可以在程序编译或运行时获取类的信息,属性或者方法。

注解解决了什么问题?

代码配置分离:我们可以将系统配置信息写入xml或者properties文件中,注解会将配置信息自动嵌入到代码中

减少样板代码:使用lombook的@Data注解,我们可以不用手动创建Getter或者Setter方法

编译时检查:使用了Override注解的方法,编译器会在编译期间就检查是否对父类方法进行了重写

运行时通过反射处理:比如spring中的component注解,运行时就使用了反射进行实现

Java IO

BIO,NIO,AIO 有什么区别?

BIO是同步阻塞IO模型,当应用程序发起read调用时,会一直阻塞,知道内核空间把数据拷贝到用户空间。其只能面对客户端连接数量较低,低并发的场景。

NIO是同步非阻塞IO模型,应用程序会一直发起read调用,在等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到数据被拷贝到用户空间。NIO相比BIO,通过轮询操作,解决了一直阻塞的情况。其也存在不停进行I/O系统调用轮询数据是否准备好导致大量消耗cpu资源的问题。

Java中的NIO其实是I/O多路复用模型,线程会发起select调用,询问内核数据是否准备好,当数据准备好后,再发起read调用,这个调用的过程依然是阻塞的。I/O多路复用模型相较于同步非阻塞IO模型,减少了不必要的系统调用,从而减少了cpu资源的消耗。Java中有一个Selector(选择器)的概念,可以被称为多路复用器,通过它,只需要一个线程便可对多个客户端进行服务,只有当某个客户端的数据准备好后,才会为其服务。

AIO是异步IO模型,是基于事件和回调机制实现的,应用发送操作请求后,会直接返回,不会阻塞,等内核空间处理完数据,会发送回调请求通知线程进行后续的操作。

集合

集合概览

说说 List, Set, Queue, Map 四者的区别?

List:存储的元素是有序,可重复的

Set:存储的元素是不可重复的

Queue:按照特定的排队顺序存储元素,存储的元素是有序,可重复的

Map:使用键值对存储数据,key是无序不可重复的,value是无序可重复的

集合框架底层数据结构总结

List:

  • ArrayList:Object[]数组
  • Vector:Object[]数组
  • LinkedList:双向链表

Set:

  • HashSet(无序,唯一):底层使用HashMap实现
  • LinkedHashSet:是HashSet的子类,底层使用LinkedHashMap实现
  • TreeSet:(有序,唯一)红黑树实现

Queue:

  • PriorityQueue:Object[]数组实现
  • DelayQueue:PriorityQueue实现
  • ArrayDeque:可扩容动态双向数组

Map:

  • HashMap:主体是数组加链表,链表是为了解决哈希冲突。在jdk1.8之后,链表长度大于阈值时,会转换为红黑树,减少查找时间。
  • LinkedHashMap:HashMap的子类,底层仍然是数组加链表或红黑树,在此基础上加入了一个双向链表。可以通过对链表的相关操作,实现访问相关逻辑。
  • HastTable:主题是数组加链表,链表是为了解决哈希冲突。
  • TreeMap:红黑树

哪些集合是线程不安全的?怎么解决呢?

ArrayList LinkedList HashMap HashSet TreeMap TreeSet这些集合是线程不安全的

可以使用java.util.concurrent包中线程安全的集合,比如ConcurrentHashMap等

list

ArrayList 与 LinkedList 区别?

  • 底层数据结构:

    ArrayList底层使用的是Object数组,LinkedList底层使用的是双向链表

  • 插入和删除受元素位置的影响:

    ArrayList插入和删除操作受元素位置的影响,比如要在头部进行插入或者删除操作,头部之后的所有元素都要向后或向前移动

    LinkedList则不受这样的影响

  • 是否支持快速随机访问

    ArrayList实现类RandomAccess接口,支持快速随机访问,即可以直接通过元素序号获取元素对象,而LinkedList则不支持

  • 内存空间:

​ LinkedList每个元素的空间占用要大于ArrayList,因为每个元素都要存放前后驱指针以及元素的值

Set

比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同

  • 同:三者都是Set接口的实现类,其中的元素都不不可重复的,并且三者都不是线程安全的。
  • 异:
    • 底层数据结构:HashSet底层使用HashMap实现。LinkedHashSet底层使用LinkedHashMap实现,数据结构是链表加哈希表,元素的插入和取出满足FIFO先入先出顺序。TreeSet底层使用红黑树实现,元素是有序的,可以采用自然排序或自定义排序
    • 应用场景:HashSet中的元素是无序的,用于对元素插入和取出没有要求的场景。LinkedHashSet用于需要元素先进先出的场景。TreeSet用于需要自定义元素插入和取出顺序的场景

Map

HashMap 和 Hashtable 的区别

  • 线程安全:HashMap是线程不安全,Hashtable是线程安全的,因为其中的方法都是使用了synchronized修饰
  • 效率:Hashtable效率较低,基本被淘汰
  • 对key和value为null值的支持:HashMap支持key或者value为null值,而Hashtable不支持
  • 初始容量大小和扩容大小不同:
    • 对于没有指定初始容量,HashMap会每次扩容两倍,Hashtable会扩容为原来的2n+1
    • 对于指定了初始容量,Hashtable会使用指定的容量大小,HashMap则将其扩充为2的幂次方作为初始容量的大小
  • 底层数据结构:在链表达到阈值时,HashMap会将其转换为红黑树,以减少搜索时间,而Hashtable没有这样的机制

HashMap 和 HashSet 区别

HashSet底层就是基于HashMap实现的,大部分方法都是调用HashMap的方法

HashMap 和 TreeMap 区别

两者都继承自AbstractMap,但TreeMap另外实现了NavigableMap接口和SortedMap接口,有了对元素的键排序的能力以及对元素搜索的能力

HashMap 的底层实现

  • 通过数组和链表或者红黑树实现,每个数组元素被称为一个桶,每个桶中存放链表,链表是为了解决哈希冲突,当不同的键通过哈希计算映射到同一个桶中时,会将其添加到链表中
  • 在Jdk1.8之后,当链表长度超过阈值(默认8)且数组长度大于等于64时,会将链表转换为红黑树,将查询时间复杂度从O(n)降低至O(log n)

HashMap 的长度为什么是 2 的幂次方

使用2的幂次方作为HashMap的长度,主要是为了优化哈希表的性能:

  • 使用hash&(length-1)位运算替代hash%length:cpu进行取模操作需要多个时钟周期,而位运算只需要一个时钟周期
  • 减少哈希冲突:当长度为2的幂次方时,length-1的二进制表达全为1,如果不为1,会导致hash最低位是1的索引无法映射到桶中,发送哈希冲突
  • 扩容时更平滑:Hashmap扩容时默认会扩容为原大小的两倍,这意味着新的大小只比原大小的最高位多了一个1
    • 计算新索引只需要先进行判断hash对应的新增高位是0还是1:
    • 如果hash&oldCapacity==0,则索引不变
    • 否则,新索引=原索引+原大小(oldCapacity)
  • 保证容量对齐:2的幂次方容量便于内存对齐,提高缓存命中率

ConcurrentHashMap 和 Hashtable 的区别?

  • 底层数据结构:JDK1.7时,ConcurrentHashMap 使用分段数组+链表实现,JDK1.8时,与HashMap相同,使用数组+链表/红黑树实现。Hashtable使用数组+链表的形式实现。
  • 实现线程安全的方式
    • ConcurrentHashMap在JDK1.7时使用分段锁,对数组进行分段加锁,每一把锁只锁定数组中一部分内容,多线程访问数组内不同数据段的数据,就不会存在锁竞争,提高并发访问。JDK1.8取消了分段锁,采用Node数组+链表+红黑树的数据结构实现,并发控制采用synchronized桶级别锁(仅对发生哈希冲突的桶加锁)+CAS来操作,并发度更高。
    • Hashtable仅使用synchronized实现,一锁就会锁住整张表,并发度低。

ConcurrentHashMap 线程安全的具体实现方式/底层具体实现

Java7 ConcurrentHashMap 存储结构

Java8 ConcurrentHashMap 存储结构

  • JDK1.7使用分段锁,每段数据独立加锁,JDK1.8使用synchorinzed桶加锁和CAS无锁算法实现线程安全

并发

什么是线程和进程?线程与进程的关系,区别及优缺点?

  • 进程是程序的一次执行过程,是系统运行程序的基本单位
  • 线程是比进程更小的执行单位,一个进程在执行过程中可以产生多个线程
  • 线程与进程的关系,区别及优缺点:
    • 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。

为什么要使用多线程?

  • 从计算机底层上来说:线程是程序最小的执行单位,线程的切换和调度成本远小于进程。另外,多核CPU时代意味着多个线程可以同时运行,这大大减少了线程上下文切换的开销
  • 从当代互联网发展趋势来说:现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程可以大大提高系统的并发能力和性能

什么是线程上下文切换?

线程在执行过程中会有自己的运行条件和状态(也称上下文),在发生如下情况时,线程会从占用CPU状态中退出

  • 主动让出CPU,比如调用了sleep(),wait()等
  • 时间片用完,因为操作系统要防止一个线程或者进程长时间占用CPU导致其他线程或进程饿死
  • 调用了阻塞类型的系统中断,比如IO请求,线程被阻塞
  • 被终止或者结束运行

这其中前三种情况都会发生线程切换,线程切换意味着需要保存当前线程的上下文,留待线程下次占用CPU的时候恢复现场。并加载下一个将要占用CPU的线程上下文。这就是线程的上下文切换。

什么是线程死锁?如何避免死锁?

线程死锁就是多个线程被阻塞,它们中的一个或多个都在等待某个资源被释放,由于线程被无期限地阻塞,因此程序永远不可能正常终止。

产生死锁由四个必要条件:

  • 互斥条件:该资源任意时刻只能被一个线程占用
  • 请求与保持条件:一个线程请求资源被阻塞时,对已有的资源会保持不放
  • 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完之后才会释放
  • 循环等待条件:若干线程形成一种头尾相连的循环等待资源关系

如何预防死锁? 破坏死锁的产生的必要条件即可:

  1. 破坏请求与保持条件:一次性申请所有的资源。
  2. 破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  3. 破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件

乐观锁和悲观锁了解么?如何实现乐观锁?

  • 悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以共享资源每次只给一个线程使用并加锁,其它线程阻塞,用完后再把资源转让给其它线程
  • 乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。

乐观锁一般会使用版本号机制或 CAS 算法实现,CAS 算法相对来说更多一些。

版本号机制

一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

CAS 算法

CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。

CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。

原子操作 即最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。

CAS 涉及到三个操作数:

  • V:要更新的变量值(Var)
  • E:预期值(Expected)
  • N:拟写入的新值(New)

当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了 V,则当前线程放弃更新。

MySql

MySQL 字段类型

char 和 varchar 的区别是什么?

char是定长字符串,varchar是变长字符串

char在存储时会在右边填充空格以达到指定长度,检索时会去掉空格,varchar会在存储时额外使用1到2个字节来存储字符串长度,检索时不做处理

char更适合存放长度较小且不变的字符串,比如Bcrypt或者md5加密过后的用户密码

varchar更适合存放长度较长经常变化的字符串,比如用户名称和描述信息

varchar(100)和 varchar(10)的区别是什么?

varchar(100)和 varchar(10)分别表示能存放最大字符长度为100和50的变长字符串,虽然两者能存放的字符串长度范围不同,但在存放相同长度的字符串时,两者占用硬盘的存储空间是一样的,但在内存中时,varchar(100)会分配更大的内存空间处理数据

decimal 和 float/double 的区别是什么?存储⾦钱应该⽤哪⼀种?

decimal是定点数,float/double是浮点数,decimal能存储精确的小数值,float/double只能存储近似的小数值,存储金钱时应该使用decimal

为什么不推荐使用 TEXT 和 BLOB?

TEXT相较于char和varchar,能存储更长的文本数据,比如博客文章等

BLOB主要用于存储大二进制对象,比如图片,音频文件等

使用二者会有如下缺点:

  • 无默认值
  • 在创建临时表时只能在内存中创建,只能在硬盘中创建
  • 检索效率较低
  • 不能直接创建索引,需要指定前缀长度

MySQL 存储引擎

MySQL ⽀持哪些存储引擎?默认使⽤哪个?

我知道常见的几个存储引擎,如下

  • InnoDB:事务存储引擎,是MySQL中唯一支持事务的引擎,也是MySql8之后的默认引擎。
  • MyISAM:MySQL5之前的默认引擎
  • Memory:数据存储在内存中,读写速度极快。
  • CSV

MyISAM 和 InnoDB 有什么区别?

InnoDB支持行级锁和表级锁,而MyISAM仅支持表级锁

InnoDB支持事务,MyISAM不支持

InnoDB支持外键,MyISAM不支持

InnoDB支持数据库崩溃后的安全恢复,MyISAM不支持

InnoDB支持MVCC,MyISAM不支持

InnoDB性能强于MyISAM,其索引文件就是数据文件

MySQL 索引

索引是什么?

索引是一种用于快速查询和检索数据的数据结构,其本质可以看作是一种排序好的数据结构。

  • 通过使用索引,能够大大加快数据的检索速度,减少i/o操作
  • 通过创建唯一性索引,能保证表中每一行数据的唯一性

为什么索引能提⾼查询速度?

  • 避免全表扫描,没有索引会进行全表扫描
  • 索引通常使用哈希表或B+树等数据结构,能快速定位数据
  • 索引通常是有序的,能直接通过范围查询between,>,<查询排序好的范围内的数据

索引底层的数据结构了解么?Hash 索引和 B+树索引优劣分析

数据库索引底层的数据结构一般使用Hash表或者B+树。

Hash表索引是通过哈希函数将键映射到哈希桶中,每个桶存储指向实际数据的指针。

优势:

  • 对于等值查找,速度非常快,时间复杂度为O(1)

劣势:

  • 不适合范围查询
  • 不支持排序
  • 不支持前缀查询,比如like关键字的查询
  • 有可能发生哈希冲突,即两个不同的键对应到一个哈希桶中

B+树是自平衡树,数据存储在叶子节点上,叶子节点通过链表连接,形成有序队列

优势:

  • 支持范围查询
  • 支持排序
  • 支持前缀查询

缺点:

  • 对于等值查询来说,相较于hash表索引速度更慢
  • 需要额外存储指针数据,占用更多空间

聚集索引和⾮聚集索引的区别?⾮聚集索引⼀定回表查询吗?

聚集索引:索引结构和数据存放在一起,比如InooDB中的主键索引

非聚集索引:索引结构和数据分开存在,MyISAM中的主键和非主键索引,二级索引(辅助索引)都是非聚集索引

聚集索引相较于非聚集索引,查找速度非常快,但是更新代价更大

非聚集索引更新代价更小,但是可能发生二次查询(回表查询),当根据索引查询到对应的指针或主键后,可能还需要根据指针或主键再回到数据文件或表中查询

二者都依赖有序的数据

非聚集查询是否要回表查询依赖于是否使用了覆盖索引,如果我们要查询的字段就是索引的字段时,就不需要回表查询。

什么是覆盖索引?

覆盖索引就是索引中的字段包含我们需要查询的字段,就可以直接根据索引查找到数据。

最左前缀匹配原则了解么?

最左前缀匹配原则是指在使用联合索引时,MySQL会根据索引中字段的顺序,从左到右依次匹配查询条件中的字段,如果查询条件中的字段与索引中最左侧字段相同,那么MySql就会使用索引过滤数据,这样可以提高查询效率。

索引这么多优点,为什么不对表中的每⼀个列创建⼀个索引呢?(使⽤索引⼀定能提⾼查 询性能吗?)

索引是需要维护的,每当数据发生增删改时,相对应的索引也会发生改变,过多的索引会降低性能,增加维护成本

B+树做索引⽐红⿊树好在哪⾥?

  • 范围查询优势:B+树叶子节点通过指针相连形成有序链表,更适合范围查询。
  • 稳定性:B+树发生增删操作时,只会影响叶子节点,稳定性更高。红黑树会频繁调整树结构,造成性能波动。

如何查看某条 SQL 语句是否⽤到了索引?

可以使用EXPLAIN来分析SQL语句的执行计划。执行计划是指一条SQL语句在经过MySQL查询优化器优化后,具体的执行方式。

MySQL 事务

事务的四⼤特性了解么?

事务的四大特性是ACID,分别是原子性,一致性,隔离性和持久性

  • 原子性:事务是最小的执行单位,不可分割。要求事务相关的动作要么全部执行,要么全部不执行。
  • 一致性:要求事务执行前后的数据是一致的。比如收款方和转账方在事务操作前后的总金额是不变的。
  • 隔离性:在并发操作中,一个用户的事务不能被其他事务所影响。
  • 持久性:当事务被提交后,对数据库中数据的改变是持久的。即使数据库发送故障也不应该对其有任何影响。

并发事务带来了哪些问题?不可重复读和幻读有什么区别?

并发事务带来了脏读,丢失修改,不可重复读和幻读的问题。

  • 脏读:一个事务读取数据并对这个数据进行了修改,这时第二个事务访问了这个还未被提交的数据,第一个事务突然回滚,那么第二个事务读到的数据就是脏数据
  • 丢失修改:一个事务访问了数据,此时第二个事务也访问了数据,第一个事务先对数据进行了修改,紧接着第二个事务也对数据进行了修改,那么第一个修改的数据就会丢失
  • 不可重复读:第一个事务在多次读取数据,此时第二个事务也读取了这个数据并进行了修改,此时第一个事务前后读取的数据就会发送不一致,这就是不可重复读
  • 幻读:第一个事务在多次读取数据,此时另外一个事务插入了数据,那么第一个事务前后读取到的数据数量就会不一致

不可重复读相较于幻读强调的的是事务多次读取数据发现已存在的数据记录被修改,幻读强调的是事务多次读取数据发现出现新增的数据。

幻读相当于不可重复读的特殊情况,将两者分开的原因是解决两者的方案不一样:

  • 对于不可重复读,执行delete和update操作的时候,只需要加记录锁,就能保证事务安全

  • 而对于幻读,由于数据是新增的,原来并不存在,所以不能加记录锁解决,为了避免插入新数据,这时需要间隔锁,也就是执行insert需要临键锁(记录锁+间隔锁)解决。

MySQL 事务隔离级别?默认是什么级别?

MySQL有四个事务隔离级别,分别是读取未提交,读取已提交,可重复读,可串行化

  • 读取未提交:最低的事务隔离级别,允许读取尚未提交的数据变更,可能导致脏读,不可重复读,和幻读
  • 读取已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读和不可重复读也有可能发生
  • 可重复读:对同一字段多次读取结果是一致的,除非数据由当前事务自己所修改,可以阻止脏读和不可重复读
  • 可串行化:最高的隔离级别,完全服从ACID。所有的事务依次执行,这样事务之间就不可能发生干扰,可以解决脏读,不可重复读和幻读

默认是可重复读隔离级别

MySQL 的隔离级别是基于锁实现的吗?

是基于锁和MVCC机制共同实现的。

可串行化是通过锁实现的,可重复读和读取已提交是基于MVCC机制实现的。

InnoDB 对 MVCC 的具体实现

  1. Undo Log:
    • 事务修改数据时,旧版本数据会存入Undo Log,用于构建一致性视图
  2. ReadView:
    • 事务启动时生成ReadView,记录当前活跃事务ID列表。
    • 通过ReadView判断当前数据版本是否可见,如果数据版本的事务ID存在于活跃事务列表中,则不可见
  3. 版本链:
    • 每条记录通过Undo Log和事务ID构建一条版本链,记录所有历史版本
  4. 可见性判断:
    • 快照读时,InooDB沿版本链找到可见的数据版本:事务ID小于ReadView最小ID或不在活跃列表中的版本可见

MySQL 锁

表级锁和行级锁了解吗?有什么区别?

区别:

  • 表级锁:针对非索引字段加锁,会锁住整张表,容易发生锁冲突,并发效率极低,但是加锁快
  • 行级锁:针对索引字段加锁,只针对当前操作的行进行加锁,并发效率高,加锁慢

MyISAM只支持表级锁,一锁就会锁住整张表,并发时性能低

InnoDB不仅支持表级锁,还支持行级锁,并发性能高

哪些操作会加表级锁?哪些操作会加⾏级锁?请简单举例说⼀下。

InnoDB中对table进行操作会加表级锁:

  • ALTER TABLE
  • TRUNCATE TABLE
  • DROP TABLE

对行操作会加行级锁:

  • UPDATE
  • SELECT
  • DELETE
  • INSERT

InnoDB 有哪几类行锁?

  • 记录锁:属于单行记录上的锁
  • 间隔锁:锁定一个范围,不包括记录本身
  • 临键锁:相当于记录锁加间隔锁,锁定一个范围,包括记录本身。主要用于解决幻读问题,因为记录锁只能锁住存在的数据,对于新增的数据,就需要间隔锁。

当前读和快照读有什么区别?

1. 当前读(Current Read)

  • 定义:当前读是指读取数据的最新版本,即当前时刻数据库中最新的数据。当前读通常用于需要获取最新数据的场景,比如UPDATE、DELETE、SELECT ... FOR UPDATE和SELECT ... LOCK IN SHARE MODE等操作。
  • 一致性:当前读会读取最新的、已提交的数据,因此它不会出现“脏读”现象。
  • 并发控制:当前读通常会对读取的数据加锁(如行锁、间隙锁等),以确保在读取数据期间,其他事务不能修改这些数据,从而保证数据的一致性。
  • 场景:适用于需要获取最新数据的场景,尤其是在需要对数据进行修改或加锁的情况下。

2. 快照读(Snapshot Read)

  • 定义:快照读是指读取数据在某个特定时间点的版本。MySQL的默认隔离级别(如REPEATABLE READ)下,事务中的SELECT操作通常使用快照读。快照读基于MVCC(多版本并发控制)机制,读取事务开始时的数据版本。
  • 一致性:快照读通过MVCC机制保证了读取的数据在事务期间是一致的,即使其他事务对数据进行了修改,当前事务看到的仍然是事务开始时或第一个SELECT操作时的数据版本。
  • 并发控制:快照读不会对数据加锁,因此不会阻塞其他事务的写操作,适合高并发的读操作场景。
  • 场景:适用于只读事务或不需要获取最新数据的场景,如报表查询、数据分析等。

MySQL 如何使⽤乐观锁和悲观锁?

悲观锁的基本思想是假设并发事务之间会发生冲突,因此在操作数据时直接对数据进行加锁,确保在锁释放前其他事务无法修改数据。

实现悲观锁的方式

MySQL中,悲观锁可以通过以下方式实现:

SELECT ... FOR UPDATE
  • 它对查询结果集中的行加排他锁(X锁),其他事务不能对这些行进行修改或加锁。
  • 适用于需要对数据进行修改的场景。
SELECT ... LOCK IN SHARE MODE
  • 它对查询结果集中的行加共享锁(S锁),其他事务可以读但不能写。
  • 适合需要确保数据在读取期间不被修改的场景。

乐观锁的基本思想是假设并发事务之间不会产生冲突,因此在操作数据时不直接加锁,而是在提交事务时检查数据是否被修改。如果数据被修改,则回滚事务或重试。

实现乐观锁的方式

乐观锁通常通过版本号(Version)时间戳(Timestamp)来实现:

MySQL日志

MySQL 中常⻅的⽇志有哪些?

  • 错误日志(error log) :对 MySQL 的启动、运行、关闭过程进行了记录。
  • 二进制日志(binary log,binlog) :主要记录的是更改数据库数据的 SQL 语句。
  • 一般查询日志(general query log) :已建立连接的客户端发送给 MySQL 服务器的所有 SQL 记录,因为 SQL 的量比较大,默认是不开启的,也不建议开启。
  • 慢查询日志(slow query log) :执行时间超过 long_query_time秒钟的查询,解决 SQL 慢查询问题的时候会用到,没有使用索引的sql语句同样会被记录在慢查询日志里
  • 事务日志(redo log 和 undo log) :redo log 是重做日志,undo log 是回滚日志。
  • 中继日志(relay log) :relay log 是复制过程中产生的日志,很多方面都跟 binary log 差不多。不过,relay log 针对的是主从复制中的从库。
  • DDL 日志(metadata log) :DDL 语句执行的元数据操作。

慢查询⽇志有什么⽤?

慢查询日志记录了执行时间超过 long_query_time(默认是 10s,通常设置为1s)的所有查询语句,在解决 SQL 慢查询(SQL 执行时间过长)问题的时候经常会用到。

找到慢 SQL 是优化 SQL 语句性能的第一步,然后再用EXPLAIN 命令可以对慢 SQL 进行分析,获取执行计划的相关信息。

binlog 主要记录了什么?

主要记录的是所有更改数据库数据的 SQL 语句,MySQL 数据库的数据备份、主备、主主、主从都离不开 binlog,需要依靠 binlog 来同步数据,保证数据一致性。

redo log 如何保证事务的持久性?

Redis

Redis 基础

Redis 为什么这么快?

  1. Redis基于内存,内存访问速度比磁盘访问速度高很多
  2. Redis基于Refactor模式开发了一套高效的事务处理模型,主要是单线程事件循环和I/O多路复用
  3. Redis内置了多种优化过的数据类型,性能非常高
  4. Redis通信协议实现简单且解析高效

Redis 数据类型

Redis 有哪些数据结构?

常用的有8种:

  • 5种基本数据类型:String(字符串),Hash(散列),List(列表),Set(集合),Zset(有序集合)
  • 3种特殊数据类型:HyperLogLog(基数统计),Bitmap(位图),Geospatial(地理位置)

Redis 持久化机制(重要)

宕机了,Redis 如何避免数据丢失?

可以通过Redis的持久化机制,RDB(快照)和AOF(只追加文件)解决

  • RDB:可以通过创建快照来获取存储在内存中的数据在某个时间点上的副本,可以对快照备份,可以将快照复制到其他服务器形成服务器副本,也可以留在原地用于重启时恢复
  • AOF:开启AOF持久化后每执行一条修改Redis中数据的指令,就会将其先写入AOF缓冲区,然后再写入系统内核缓冲区用于同步AOF文件,最后通过fsync策略同步到AOF文件中

如何选择 RDB 和 AOF?

  • 如果保存的数据丢失一些也没问题的话,可以使用RDB,因为RDB备份文件相比AOF小的多,解析速度也更快,AOF需要执行每一条指令
  • 如果对数据安全性要求比较高的话,建议开启RDB和AOF持久化或RDB和AOF混合持久化。

Redis 线程模型(重要)

Redis 是单线程,那怎么监听⼤量的客户端连接呢?

Redis通过I/O多路复用来实现监听大量的客户端连接,它它会将感兴趣的事件及类型注册到内核中并监听每个事件是否发生。

这样可以不在多创建线程的情况下监听多个客户端连接,减少了资源消耗。

Redis6.0 之前为什么不使⽤多线程?

  • 单线程操作简单容易维护
  • Redis的性能瓶颈不在cpu,主要存在于内存和网路上
  • 多线程可能存在死锁,线程上下文切换问题,严重可能影响性能

Redis6.0 之后为何引⼊了多线程?

主要是为了提高网络I/O读写性能

Redis 性能优化(重要)

Redis 批量操作的⽅式有哪些?

  • 原生批量操作命令
  • pipeline:不满足原子操作,多个pipeline可能是交叉执行的
  • Lua脚本:Lua脚本执行时不会有其他脚本或者Redis命令插入,可以看作是原子性操作

Redis ⼤ key 有什么危害?如何排查和处理?

大key一般指一个key对应的value所占用的内存非常大,就称其为大key

一般产生的原因有:

  • 程序设计不当,比如用String类型存储了较大的文件对应的二进制数据
  • 业务规模考虑不周到,比如没有预料到集合类型中数据的快速增长
  • 没有及时清理垃圾数据,比如哈希表中存放了过多的无用键值对

危害:

  • 客户端超时阻塞:Redis是单线程的,而操作大key时会比较耗时们就会导致Redis阻塞,对应的客户端就是超时无响应
  • 网络阻塞:如果一个大key内容有1MB的话,每秒有1000访问量,那么每秒就会有1000MB的数据传输,这对千兆及以下的服务器是灾难性的,会导致网路阻塞
  • 工作线程阻塞:使用del删除key时,会阻塞工作线程,这样就会导致没法访问后续请求

排查和处理:

  • 排查:
    • 使用Redis自带的--bigkeys参数
    • 使用Redis的SCAN命令查找key,再使用STRLEN等方法返回其对应的长度或元素数量
  • 处理:
    • 分割bigkey:将一个bigkey分割成多个小key。
    • 手动清理:Redis4.0以上可以使用UNLINK指令异步删除一个或多个指定的key。Redis4.0以下可以使用SCAN命令结合DEL命令;来分批次删除
    • 采用合适的数据结构:比如二进制数据不用String存储,使用HyperLogLog统计页面UV,Bitmap保存状态信息
    • 开启lazy-free(惰性删除):lazy-free是Redis4.0引入的,目的是为了让Redis采用异步方法延迟释放key所占的内存,并且将该操作交给子线程处理,避免阻塞主线程

如何发现 Redis 热 Key,有哪些解决⽅案?

如何发现热Key:

  • 使用--hotkeys参数,该参数能返回key的所有被访问次数,前提是要将Redis Server 的 maxmemory-policy 参数设置为 LFU 算法
  • 使用MONITOR命令,该命令能实时查看 Redis 的所有操作的方式
  • 根据业务情况提前预估

解决方案:

  • 读写分离:主节点处理写请求,从节点处理读请求
  • 二级缓存:将hotkey存放一份到JVM本地内存中

为什么会有慢查询命令?

Redis中虽然大部分命令的时间复杂度都是O(1),但也有一些命令的时间复杂度为O(n),这些指令的执行可能会导致慢查询。

比如:

  • KEY *:会返回符合规则的所有key
  • HGETALL:会返回一个Hash中所有的键值对
  • LRANGE:会返回List中指定范围内的元素
  • SMEMBERS:返回Set中的所有元素

如何找到慢查询命令?

在Redis配置文件中,可以使用slowlog-log-slower-than 参数设置耗时命令的阈值,并使用 slowlog-max-len 参数设置耗时命令的最大记录条数。

当Redis中的命令记录时间超过设置的阈值时,就会将该命令记录在慢查询日志(slog log)中

为什么会有 Redis 内存碎⽚?如何清理 Redis 内存碎片?

内存碎片就是操作系统分配的连续内存空间使用过后剩余的不可用的空闲内存

产生原因:

  • Redis存储数据的时候向操作系统申请了比实际数据所需更大的内存空间
  • 频繁修改Redis中的数据,比如删除一个数据,而Redis不会轻易将这个数据所占的内存空间返还给操作系统

清理:

Redis4.0版本以后自带了内存整理,可以避免内存碎片率过大的问题。

直接通过 config set 命令将 activedefrag 配置项设置为 yes 即可。

Redis ⽣产问题(重要)

什么情况会出现缓存穿透?有哪些解决办法?

情况:大量请求的 key 是不合理的,根本不存在于缓存中,也不存在于数据库中

解决办法:

  • 最好的办法:做好参数校验,抛出异常返回给客户端
  • 缓存无效key:即使没有在缓存和数据库中查到,也将key缓存到Redis中,这样可以避免对数据库造成压力。缺点是可能缓存大量不相同的无效key
  • 布隆过滤器:通过它我们可以非常方便地判断一个数据是否存在于海量数据中。具体实现就是将所有用户可能的请求存放到布隆过滤器中,用户请求不在其中就返回请求参数错误,在其中再对缓存或数据库进行访问。缺点是有概率造成误判
  • 接口限流:根据用户和IP进行限流,对于异常频繁的访问行为,可以采取黑名单机制,将异常IP列入黑名单

什么情况会出现缓存击穿?有哪些解决办法?

情况:请求的key是热点数据,该数据存在于数据库中,但不存在于缓存中(通常是缓存过期了),大量请求访问到数据库上导致宕机

解决方法:

  • 永不过期:设置热点数据永不过期
  • 提前预热:将热点数据提前缓存到Redis中,设置合理的过期时间,比如秒杀场景下秒杀结束过后数据才过期
  • 加锁:在缓存失效后,设置互斥锁保证同一时间只有一个请求能去查询数据库并更新缓存

什么情况会出现缓存雪崩?有哪些解决办法?

情况:缓存在同一时间大量失效,导致所有请求都直接落到了数据库上,对数据库造成了巨大压力。

解决办法:

  • 设置随机失效时间:为缓存设置随机失效时间,避免同时间大量过期
  • 提前预热:将热点数据提前缓存到Redis中,设置合理的过期时间,比如秒杀场景下秒杀结束过后数据才过期
  • 持久缓存策略:设置缓存永不过期

缓存预热如何实现?

  1. 使用定时任务:设置定时任务将数据从数据库中取出存入缓存中
  2. 使用消息队列:比如Kafka,将数据库中的热点数据的主键或者 ID 发送到消息队列中,再有缓存服务消费消息队列中的数据从数据库中取出存入缓存。

Spring

什么是 Spring 框架?

Spring 是一款开源的轻量级 Java 开发框架,一般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发,比如说 Spring 支持 IoC(Inversion of Control:控制反转) 和 AOP(Aspect-Oriented Programming:面向切面编程)、可以很方便地对数据库进行访问、可以很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持 RESTful Java 应用程序的开发。

Spring 包含的模块有哪些?

Core Container

Spring 框架的核心模块,也可以说是基础模块,主要提供 IoC 依赖注入功能的支持。Spring 其他所有的功能基本都需要依赖于该模块,我们从上面那张 Spring 各个模块的依赖关系图就可以看出来。

  • spring-core:Spring 框架基本的核心工具类。
  • spring-beans:提供对 bean 的创建、配置和管理等功能的支持。
  • spring-context:提供对国际化、事件传播、资源加载等功能的支持。
  • spring-expression:提供对表达式语言(Spring Expression Language) SpEL 的支持,只依赖于 core 模块,不依赖于其他模块,可以单独使用。

AOP

  • spring-aspects:该模块为与 AspectJ 的集成提供支持。
  • spring-aop:提供了面向切面的编程实现。
  • spring-instrument:提供了为 JVM 添加代理(agent)的功能。 具体来讲,它为 Tomcat 提供了一个织入代理,能够为 Tomcat 传递类文 件,就像这些文件是被类加载器加载的一样。没有理解也没关系,这个模块的使用场景非常有限

Data Access/Integration

  • spring-jdbc:提供了对数据库访问的抽象 JDBC。不同的数据库都有自己独立的 API 用于操作数据库,而 Java 程序只需要和 JDBC API 交互,这样就屏蔽了数据库的影响。
  • spring-tx:提供对事务的支持。
  • spring-orm:提供对 Hibernate、JPA、iBatis 等 ORM 框架的支持。
  • spring-oxm:提供一个抽象层支撑 OXM(Object-to-XML-Mapping),例如:JAXB、Castor、XMLBeans、JiBX 和 XStream 等。
  • spring-jms : 消息服务。自 Spring Framework 4.1 以后,它还提供了对 spring-messaging 模块的继承。

Spring Web

  • spring-web:对 Web 功能的实现提供一些最基础的支持。
  • spring-webmvc:提供对 Spring MVC 的实现。
  • spring-websocket:提供了对 WebSocket 的支持,WebSocket 可以让客户端和服务端进行双向通信。
  • spring-webflux:提供对 WebFlux 的支持。WebFlux 是 Spring Framework 5.0 中引入的新的响应式框架。与 Spring MVC 不同,它不需要 Servlet API,是完全异步。

Messaging

spring-messaging 是从 Spring4.0 开始新加入的一个模块,主要职责是为 Spring 框架集成一些基础的报文传送应用。

Spring Test

Spring 团队提倡测试驱动开发(TDD)。有了控制反转 (IoC)的帮助,单元测试和集成测试变得更简单。

Spring 的测试模块对 JUnit(单元测试框架)、TestNG(类似 JUnit)、Mockito(主要用来 Mock 对象)、PowerMock(解决 Mockito 的问题比如无法模拟 final, static, private 方法)等等常用的测试框架支持的都比较好。

谈谈自己对于 Spring IoC 的了解

IoC(Inversion of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

为什么叫控制反转?

  • 控制:指的是对象创建(实例化、管理)的权力
  • 反转:控制权交给外部环境(Spring 框架、IoC 容器)

Spring Bean 的⽣命周期说⼀下

  1. 创建Bean的实例:Bean容器首先会找到配置文件中Bean的定义,然后通过Java反射API来创建Bean的实例
  2. Bean属性赋值/填充:为Bean设置相关属性和依赖,例如@Autowired 等注解注入的对象,setter方法或构造函数注入依赖和值
  3. Bean 初始化:如果实现了 *.Aware接口,就调用相应的方法。
  4. 销毁Bean:销毁并不是说马上把Bean销毁掉,而是先记录下销毁Bean的方法,将来要销毁Bean或者销毁容器的时候,就调用这些方法去释放Bean所持有的资源

Spring 中的 bean 的作⽤域有哪些?

singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。

prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续 getBean() 两次,得到的是不同的 Bean 实例。

request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。

session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。

application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。

websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。

谈谈自己对于 AOP 的了解

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理

AOP 切面编程涉及到的一些专业术语:

术语 含义
目标(Target) 被通知的对象
代理(Proxy) 向目标对象应用通知之后创建的代理对象
连接点(JoinPoint) 目标对象的所属类中,定义的所有方法均为连接点
切入点(Pointcut) 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点)
通知(Advice) 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情
切面(Aspect) 切入点(Pointcut)+通知(Advice)
Weaving(织入) 将通知应用到目标对象,进而生成代理对象的过程动作

计算机网络

HTTP 和 HTTPS 有什么区别?(重要)

端口号:HTTP 默认是 80,HTTPS 默认是 443。

URL 前缀:HTTP 的 URL 前缀是 http://,HTTPS 的 URL 前缀是 https://

安全性和资源消耗:HTTP运行在TCP协议之上,所有传输的内容都是明文。HTTPS是运行在SSL/TLS之上HTTP协议,传输的内容是经过对称加密的。总的来说HTTPS比HTTP安全性更高,但是资源消耗更多。

SEO(搜索引擎优化):搜索引擎通常会更青睐使用 HTTPS 协议的网站,因为 HTTPS 能够提供更高的安全性和用户隐私保护。使用 HTTPS 协议的网站在搜索结果中可能会被优先显示,从而对 SEO 产生影响。

posted @ 2025-04-10 19:17  NONAME-X  阅读(53)  评论(1)    收藏  举报